nano_core 1.0.6
nano_core: ^1.0.6 copied to clipboard
A lightweight reactive architecture framework and design system toolkit for Flutter multiplatform applications.
Nano Core #
A lightweight reactive architecture framework and design system toolkit for Flutter multiplatform applications.
๐ค AI-Powered Architecture & Remote MCP Server #
nano_core comes with native support for the Model Context Protocol (MCP), connecting your AI assistants directly to the framework's architecture, historical changelog matrix, and safety guardrails.
โก Architecture & Orchestration Flow #
๐ IDE Configuration (Zero-Install) #
Option 1: Claude Desktop / Antigravity / VS Code (Universal HTTP Proxy)
{
"mcpServers": {
"nano-core": {
"command": "npx",
"args": [
"-y",
"mcp-proxy",
"https://api.nanodevs.com.br/mcp"
]
}
}
}
Option 2: Cursor IDE (Native Remote SSE)
{
"mcpServers": {
"nano-core": {
"url": "https://api.nanodevs.com.br/sse"
}
}
}
๐ญ 4 Specialized AI Personas #
| Persona | Purpose |
|---|---|
| ๐งโโ๏ธ Nano Architect | Designs clean architecture blueprints, state models, form controllers (NanoFormController), and scaffolding. |
| ๐ก๏ธ Nano Migration Master | Calculates version migration paths, resolves breaking changes, and updates deprecated APIs safely. |
| ๐ Nano Reviewer | Audits code for controller memory leaks, NanoResult error handling, and lint rules. |
| ๐งช Nano QA | Generates bulletproof unit and widget test suites with mocked dependencies. |
Features #
๐งญ Navigation & Declarative Routing #
-
๐ฑ NanoApp: Zero-boilerplate root application widget automatically configuring
NanoRouter,MaterialApp, themes, and localizations. -
๐งญ NanoRouter & Declarative Routes (NanoRouteBase): Intuitive zero-dependency declarative router supporting polymorphic route hierarchies (
NanoRouteBase), standard routes (NanoRoute), persistent tab shells (NanoShellRoute), animated transitions (NanoAnimatedRoute), route groups (NanoGroupRoute), typed sub-routes (NanoDetailsRoute<Args>), access-guarded routes (NanoProtectedRoute), and redirects (NanoRedirectRoute). -
๐ NanoShellScaffold, NanoShellTab & NanoShellSubView: Persistent navigation shell scaffold managing primary tabs with keep-alive (
maintainState), optional contextual sub-views (e.g. notifications, search overlays), persistent floating action buttons, drawers, and automatic back-gesture handling (enablePopScope). -
๐ญ NanoRouteObserver: Granular navigation observer for screen tracking, Firebase Analytics, Datadog, breadcrumbs, and route lifecycle telemetry.
โก Reactive State Management & Architecture #
-
โก NanoController & NanoState: Clean, reactive state management built on
ChangeNotifierandListenableBuilder. -
๐ NanoViewState: Base class for structured, immutable and equatable view/page state data models.
-
๐ NanoScaffold & NanoStateObservable: Decoupled reactive base page scaffold supporting Web/Desktop headers, mobile AppBars, drawers, footers, customizable floating action buttons with positioning (
floatingActionButtonLocation), loading overlays, toasts, fallback messages, and universal state observation (NanoController, BLoC, Cubit, MobX adapters). -
๐ ๏ธ NanoCommand & NanoCommandBuilder: Encapsulated async commands for user actions and operations.
-
๐ NanoInjections, NanoDefaultInjections & NanoStatePage: Dependency injection scoping with
GetIt, default framework services registration (NanoDefaultInjections.init), modular composition, and page lifecycle binding.
๐ฆ Data Layer, HTTP & Smart Caching #
-
๐ NanoHttpClient & NanoHttpInterceptor: Standardized generic contract for decoupled HTTP communication, request/response interceptors (JWT injection, refresh tokens), built-in traffic logging (
NanoHttpLogInterceptor), and helper extensions (isSuccess,isClientError,isServerError). -
๐ฆ NanoRepository, NanoSearchRepository & NanoQueryAdapter: Automated generic CRUD repository layer, type-safe search query serialization, and domain model adapters.
-
โก NanoCache & Smart Caching: Zero-dependency in-memory caching (
NanoMemoryCache) with configurable policies (cacheFirst,networkFirst,networkOnly,cacheOnly), TTL expiration, and automatic invalidation on CRUD mutations. -
๐ Pagination & NanoPaginator: Pluggable strategies (
NanoOffsetPagination,NanoCursorPagination), reactive controller (NanoPaginator), automatic infinite scrolling widget (NanoPaginatedListView), and customizable navigation bar (NanoPaginationBar). -
๐ท๏ธ NanoEntity & NanoEquatable: Base domain entity with unique identification and value-based equality.
๐ก๏ธ Functional Safety & Reactive Forms #
-
๐ก๏ธ Functional Results (NanoResult): Modern Dart 3
sealed classhierarchy (NanoSuccess,NanoFailure) with compile-time pattern matching,fold,map, andrunAsyncsafe execution. -
๐ NanoForm & Validators: Strongly-typed form models, automatic field disposal,
BuildContexti18n support, and reactiveNanoTextFieldcomponent.
๐ Authentication, OAuth 2.0 & Security #
-
๐ NanoOAuth & NanoPkce: Zero-dependency OAuth 2.0 PKCE toolkit (RFC 7636) with built-in pure-Dart SHA-256 for secure authorization URLs, code challenge generation, token exchange payloads, and anti-CSRF callback parsing.
-
๐ NanoAuthRepository: Pure token and session lifecycle management with symmetrical storage keys, automatic token storage, and session contracts.
๐ชต Observability, Utilities & Design System #
-
๐ก NanoTelemetry & Observers (Analytics & Crashes): 100% decoupled, zero-dependency telemetry architecture multiplexing events, anti-cardinality screen tracking, breadcrumbs, and error reporting to Firebase, Sentry, Datadog, or Mixpanel.
-
๐ชต NanoLogger & NanoLogFilter: Granular structured console logger with type-safe level filtering (
NanoLogFilter), ANSI styling, method context tracking, and telemetry hooks. -
๐ NanoConnectivity: Zero-dependency cross-platform reactive network monitor (
NanoConnectivity,NanoConnectivityStatus) with seamlessNanoScaffold(connectivityBuilder: ...)integration. -
โฑ๏ธ NanoDebouncer: Flexible async execution delay for search inputs, autocomplete, and live filters with native
NanoTextField(debounceDuration: ...)support. -
๐งฉ Design System & Feedback Components: Standalone reusable UI widgets including
NanoSkeleton,NanoShimmer,NanoLoadingOverlay,NanoToast,NanoPaginatedListView,NanoPaginationBar,NanoTextField, andNanoPoweredBy. -
โจ Native Skeleton & Shimmer Loading: 100% native wave gradient shimmer and skeleton placeholders (presets: list, card, grid; primitives: box, circle, text; and GPU-accelerated ghost masking).
-
๐ฅ๏ธ NanoDeviceType & NanoEnvironment: Real-time cross-platform environment,
--dart-defineparsers, and responsive viewport width inspection.
Getting Started #
Add nano_core to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
nano_core: ^1.0.6
Quick Example #
1. Domain Entity & Adapter #
import 'package:nano_core/nano_core.dart';
class User extends NanoEntity<String> {
final String name;
const User({required super.id, required this.name});
}
class UserAdapter implements NanoAdapter<User> {
const UserAdapter();
@override
User fromMap(Map<String, dynamic> map) => User(
id: map['id'] as String,
name: map['name'] as String,
);
@override
Map<String, dynamic> toMap(User user) => {
'id': user.id,
'name': user.name,
};
}
class UserRepository extends NanoRepository<User, String> {
UserRepository({
super.endpoint = '/users',
super.adapter = const UserAdapter(),
super.client,
});
}
// Type-Safe Search with NanoSearchRepository:
class UserFilter {
final String? role;
final int page;
const UserFilter({this.role, this.page = 1});
}
class UserFilterAdapter extends NanoWriteAdapter<UserFilter> {
const UserFilterAdapter();
@override
Map<String, dynamic> toMap(UserFilter query) {
return <String, dynamic>{}
.add('page', query.page)
.addIf('role', query.role);
}
}
class UserSearchRepository
extends NanoSearchRepository<User, String, UserFilter> {
UserSearchRepository({
super.endpoint = '/users',
super.adapter = const UserAdapter(),
super.queryAdapter = const UserFilterAdapter(),
super.client,
});
}
// Search with type-safe query parameters:
// final results = await userSearchRepository.searchAll(const UserFilter(role: 'admin'));
2. View State, Controller & Injections #
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:nano_core/nano_core.dart';
// 1. Structured View State
class UsersState extends NanoViewState {
final List<User> users;
const UsersState({this.users = const []});
@override
List<Object?> get props => [users];
}
// 2. Reactive Controller
class MyController extends NanoController<UsersState> {
final UserRepository repository;
MyController({
required this.repository,
super.initialState = const UsersState(),
});
@override
Future<void> init(String? id) async {
await loadUsers();
}
// ๐ Option A (Recommended): Clean async execution with `execute()`
// Automatically emits Loading, handles try/catch, emits Error on failure, and triggers onSuccess!
Future<void> loadUsers() async {
await execute(
() => repository.getAll(),
onSuccess: (users) => emitLoaded(UsersState(users: users)),
// onError: (error) => emitError(key: 'custom_error'), // Optional custom error handler
);
}
// ๐ ๏ธ Option B: Manual control with direct emit helpers & try/catch
Future<void> loadUsersManual() async {
emitLoading();
try {
final users = await repository.getAll();
emitLoaded(UsersState(users: users));
} catch (_) {
emitError();
}
}
// ๐ก 3 Flexible Ways to Emit State in NanoController:
// 1. Direct Helper: emitLoaded(myState), emitLoading(), emitSuccess(key: ...), emitError(key: ...), emitCustom(myPayload)
// 2. Fluent State: emit(state.toLoaded(myState)), emit(state.toLoading()), emit(state.toCustom(myPayload))
// 3. Explicit Class: emit(LoadedState(myState)), emit(const LoadingState()), emit(CustomState(myPayload))
}
// 3. Page Injections Scope
class UsersInjections extends NanoInjections {
const UsersInjections() : super(scope: 'users');
@override
void binds(GetIt i) {
// 1. Initialize default core framework services:
NanoDefaultInjections.init(i, client: DioHttpClient(Dio()));
// 2. Register repository (client is automatically injected via GetIt!):
i.registerLazySingleton<UserRepository>(() => UserRepository());
// 3. Register page controller:
i.registerFactory<MyController>(
() => MyController(repository: i<UserRepository>()),
);
}
}
// 4. Page with NanoStatePage & NanoScaffold
class UsersPage extends StatefulWidget {
const UsersPage({super.key});
@override
State<UsersPage> createState() => _UsersPageState();
}
class _UsersPageState
extends NanoStatePage<UsersPage, MyController> {
@override
NanoInjections get injections => const UsersInjections();
@override
Widget build(BuildContext context) {
return NanoScaffold<UsersState, NanoMessageKey>(
controller: controller,
defaultErrorMessage: 'An unexpected error occurred',
header: (context, state) => AppBar(
title: Text(
state.data?.users.isNotEmpty == true
? 'Users (${state.data!.users.length})'
: 'Users List',
),
),
builder: (context, state) {
final users = state.data?.users ?? [];
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(users[index].name),
);
},
);
},
);
}
}
3. HTTP Client Implementation with Dio (Optional) #
nano_core remains 100% agnostic to third-party HTTP dependencies. To use Dio as your HTTP client, implement NanoHttpClient in your app:
import 'package:dio/dio.dart';
import 'package:nano_core/nano_core.dart';
class DioHttpClient extends NanoHttpClient {
final Dio _dio;
DioHttpClient(this._dio, {super.interceptors});
@override
Future<NanoHttpResponse<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.get<T>(
path,
queryParameters: queryParameters,
options: Options(headers: headers),
);
return NanoHttpResponse<T>(
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
}
@override
Future<NanoHttpResponse<T>> post<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
);
return NanoHttpResponse<T>(
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
}
@override
Future<NanoHttpResponse<T>> put<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.put<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
);
return NanoHttpResponse<T>(
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
}
@override
Future<NanoHttpResponse<T>> delete<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
);
return NanoHttpResponse<T>(
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
}
@override
Future<NanoHttpResponse<T>> patch<T>(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.patch<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
);
return NanoHttpResponse<T>(
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
}
}
Register it with interceptors at app startup with GetIt / NanoInjections:
void main() {
final client = DioHttpClient(Dio(BaseOptions(baseUrl: 'https://api.example.com')));
// Add traffic logging or custom authentication interceptors:
client.addInterceptor(const NanoHttpLogInterceptor());
GetIt.I.registerLazySingleton<NanoHttpClient>(() => client);
runApp(const MyApp());
}
4. Declarative Routing with NanoRouter, Observers & NanoApp #
Define all application routes and analytics observers in a single declarative router file:
import 'package:nano_core/nano_core.dart';
final appRouter = NanoRouter(
initialRoute: '/', // Optional: defaults to '/'
observers: [
// ๐ญ Track screens automatically with Firebase Analytics / Datadog:
NanoRouteObserver(
onRouteChange: (from, to, args) {
debugPrint('Navigated from: $from -> to: $to');
},
),
],
routes: [
// Public dashboard route with smooth fade transition:
NanoAnimatedRoute.fade(
name: 'showcase',
path: '/',
builder: (context, args) => const ShowcasePage(),
),
// Users list with nested typed detail route:
NanoRoute(
name: 'users',
path: '/users',
builder: (context, args) => const UsersPage(),
routes: [
// Sub-route: /users/detail with automatic argument typing
NanoDetailsRoute<User>(
name: 'user_detail',
builder: (context, user) => UserDetailPage(user: user),
),
],
),
// Protected area with route guard wrapping admin routes:
NanoProtectedRoute(
hasAccess: (context, args) => AuthService.isAdmin,
redirectTo: 'login',
routes: [
NanoGroupRoute(
path: '/admin',
routes: [
NanoRoute(
name: 'admin',
path: '/panel',
builder: (context, args) => const AdminPage(),
),
],
),
],
),
// Redirect / Alias route:
NanoRedirectRoute(
path: '/home',
redirectTo: 'showcase',
),
],
);
Then plug it directly into NanoApp in main.dart:
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return NanoApp(
title: 'My Nano App',
router: appRouter, // ๐งญ Configures navigatorKey, initialRoute, and onGenerateRoute
theme: AppTheme.darkTheme,
);
}
}
Navigating anywhere:
// Navigate by route name:
context.toNamed('user_detail', arguments: user);
// Navigate by path:
context.toNamed('/users/detail', arguments: user);
// Replace current screen:
context.toReplacementNamed('login');
// Pop screen:
context.back();
5. Persistent Multi-Tab Navigation (NanoShellRoute & NanoShellScaffold) #
Zero-dependency persistent shell scaffold managing multi-tab apps with state preservation (maintainState), contextual sub-views (e.g. notifications/search overlays), dynamic floating action bars, and system back gesture interception (enablePopScope).
nano_core gives you the flexibility to choose between two elegant approaches:
Approach A: Declarative Shell in NanoRouter via NanoShellRoute (Recommended)
Register persistent shells cleanly in NanoRouter via shells: (or routes:), delegating layout to your page widget without leaking UI scaffolding into router tables:
enum AppTab { home, favorites, profile, settings }
enum AppSubView { searchOverlay }
final appRouter = NanoRouter(
initialRoute: '/home',
routes: [
NanoRoute(path: '/login', builder: (_, __) => const LoginPage()),
],
shells: [
NanoShellRoute<AppTab, AppSubView>(
path: '/home',
name: 'home',
initialTab: AppTab.home,
builder: (context, controller, body) => HomePage(
body: body,
controller: controller,
),
tabs: [
NanoShellTab(value: AppTab.home, builder: (_) => const FeedPage()),
NanoShellTab(value: AppTab.favorites, builder: (_) => const FavoritesPage()),
NanoShellTab(value: AppTab.profile, builder: (_) => const ProfilePage()),
NanoShellTab(value: AppTab.settings, builder: (_) => const SettingsPage()),
],
subViews: [
NanoShellSubView(
id: AppSubView.searchOverlay,
builder: (_) => const SearchOverlayPage(),
),
],
),
],
);
Where your HomePage is a clean, encapsulated widget:
class HomePage extends StatelessWidget {
final Widget body;
final NanoShellController<AppTab, AppSubView> controller;
const HomePage({required this.body, required this.controller, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: controller.isShowingSubView
? null
: MyFloatingNavBar(
activeTab: controller.currentTab,
onTabSelected: controller.selectTab,
),
body: body,
);
}
}
Approach B: Modular Page Widget via NanoShellScaffold
Prefer building a standalone widget page? Use NanoShellScaffold directly:
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return NanoShellScaffold<AppTab, AppSubView>(
initialTab: AppTab.home,
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: (context, controller) {
if (controller.isShowingSubView) return null;
return MyFloatingNavBar(
activeTab: controller.currentTab,
onTabSelected: controller.selectTab,
);
},
tabs: [
NanoShellTab(value: AppTab.home, builder: (_) => const FeedPage()),
NanoShellTab(value: AppTab.favorites, builder: (_) => const FavoritesPage()),
NanoShellTab(value: AppTab.profile, builder: (_) => const ProfilePage()),
NanoShellTab(value: AppTab.settings, builder: (_) => const SettingsPage()),
],
subViews: [
NanoShellSubView(
id: AppSubView.searchOverlay,
builder: (_) => const SearchOverlayPage(),
),
],
);
}
}
Fluid Shell Navigation anywhere via BuildContext:
// Switch primary tab fluidly:
context.shell.selectTab(AppTab.favorites);
// Open contextual sub-view:
context.shell.openSubView(AppSubView.searchOverlay);
// Close active sub-view:
context.shell.closeSubView();
// Check if a sub-view is open:
if (context.shell.isSubViewOpen()) { ... }
// Read active tab:
final currentTab = context.shell.currentTab<AppTab>();
Guarding Shell Routes with NanoProtectedRoute:
Because NanoRoute and NanoShellRoute extend NanoRouteBase, you can guard entire multi-tab shells directly with NanoProtectedRoute:
final appRouter = NanoRouter(
initialRoute: '/home',
routes: [
NanoRoute(path: '/login', builder: (_, __) => const LoginPage()),
// Guards the persistent shell and all its tabs:
NanoProtectedRoute(
hasAccess: (context, args) => AuthService.isAuthenticated,
redirectTo: '/login',
routes: [
NanoShellRoute<AppTab, AppSubView>(
path: '/home',
builder: (context, controller, body) => HomePage(
body: body,
controller: controller,
),
tabs: [
NanoShellTab(value: AppTab.home, builder: (_) => const FeedPage()),
NanoShellTab(value: AppTab.profile, builder: (_) => const ProfilePage()),
],
),
],
),
],
);
6. Type-Safe Search, Query Adapters & Pagination with NanoPaginator #
Handle URL query parameter serialization, pagination strategies, and infinite scroll lists with zero boilerplate:
1. Define Typed Filter and Adapter
class UserFilter {
final String? name;
final String? role;
const UserFilter({this.name, this.role});
}
class UserFilterAdapter extends NanoWriteAdapter<UserFilter> {
const UserFilterAdapter();
@override
Map<String, dynamic> toMap(UserFilter query) {
return <String, dynamic>{}
.addIf('name', query.name)
.addIf('role', query.role, condition: query.role != 'all');
}
}
2. Create Search Repository with Response Strategy
class UserSearchRepository extends NanoSearchRepository<User, String, UserFilter> {
UserSearchRepository({
super.endpoint = '/users',
super.adapter = const UserAdapter(),
super.queryAdapter = const UserFilterAdapter(),
super.dataStrategy = const NanoDataStrategy.data(), // JSON:API 'data' envelope
super.client,
});
}
// Fetch all with filters & pagination (returns NanoPaginatedResult<User>):
// final result = await searchRepository.searchAll(const UserFilter(name: 'Alex'));
// print(result.items); // List<User>
// print(result.totalCount); // 150 (if reported by API)
// print(result.currentPage); // 1
// print(result.hasNext); // true
3. Response Extraction Strategies (NanoDataStrategy)
Adapt to any backend REST standard with dedicated extraction strategies:
// 1. Un-enveloped Raw Arrays (GitHub, FastAPI, Go):
const NanoDataStrategy.raw()
// 2. JSON:API / Laravel / JSend Envelope {"data": [...], "meta": {...}}:
const NanoDataStrategy.data()
// 3. Django REST Framework {"results": [...], "count": 100, "next": "..."}:
const NanoDataStrategy.results()
// 4. Google Cloud APIs {"items": [...], "totalResults": 100}:
const NanoDataStrategy.items()
// 5. Custom Envelope Key {"events": [...]}:
const NanoDataStrategy.key('events')
// 6. Custom Transformation:
NanoDataStrategy.custom(
listExtractor: (json) => json['payload']['records'],
metaExtractor: (json, headers) => NanoPaginationMeta(
totalCount: json['payload']['total'],
),
)
4. Automatic Infinite Scroll (Mobile) or Page Navigation Bar (Web)
// In Controller:
late final paginator = NanoPaginator<User>(
fetcher: (pagination) => userRepository.getAll(pagination: pagination),
);
// Option A: Mobile Infinite Scroll:
NanoPaginatedListView<User>(
paginator: controller.paginator,
itemBuilder: (context, user, index) => ListTile(title: Text(user.name)),
);
// Option B: Web / Desktop Navigation Bar:
NanoPaginationBar(
paginator: controller.paginator,
showPageSizeSelector: true,
availablePageSizes: const [5, 10, 20, 50],
);
4. Instantaneous Caching (0ms latency & Offline fallback)
Works seamlessly across Web, iOS, Android, macOS, Windows, and Linux:
// 1. Configure in-memory cache globally at startup:
NanoDefaultInjections.init(
i,
client: DioHttpClient(Dio()),
cache: NanoMemoryCache(defaultTtl: const Duration(minutes: 5)),
);
// 2. Fetch using cache-first (instant response on subsequent visits):
final users = await userRepository.getAll(cachePolicy: NanoCachePolicy.cacheFirst);
// 3. Force network update during pull-to-refresh:
final freshUsers = await userRepository.getAll(cachePolicy: NanoCachePolicy.networkOnly);
5. Customizing Specific Endpoints (Overrides)
By default, NanoRepository constructs standard REST paths ($endpoint, $endpoint/$id, $endpoint/${entity.id}). You can selectively override individual operation endpoints without repeating the base URL:
class UserRepository extends NanoRepository<User, String> {
UserRepository([super.client])
: super(
endpoint: '/users',
adapter: const UserAdapter(),
);
// Custom update path:
@override
String endpointUpdate(User entity) => '$endpoint/profile';
// Custom detail path:
@override
String endpointGetById(String id) => '$endpoint/details/$id';
// Custom creation path:
@override
String endpointCreate(User entity) => '$endpoint/register';
}
6. Authentication & Session Repository (NanoAuthRepository)
Standardized base repository for authentication, non-volatile token persistence via NanoStorage, session lifecycle, and automatic GetIt client/storage resolution:
class AuthRepository extends NanoAuthRepository<UserSession> {
AuthRepository({super.client, super.storage});
Future<bool> signInWithEmail(String email, String password) async {
final response = await client.post<Map<String, dynamic>>(
'/api/v4/auth/login',
data: {'email': email, 'password': password},
);
if (response.isSuccess && response.data != null) {
saveToken(response.data!['token']);
return true;
}
return false;
}
// Optional: override refreshSession to renew access tokens using refreshToken
@override
Future<bool> refreshSession() async {
if (refreshToken == null) return false;
final response = await client.post<Map<String, dynamic>>(
'/api/v4/auth/token',
data: NanoOAuth.buildRefreshTokenBody(refreshToken: refreshToken!),
);
if (response.isSuccess && response.data != null) {
saveToken(response.data!['token'], refreshToken: response.data!['refresh_token']);
return true;
}
return false;
}
// Optional: override restoreSession if this repository hydrates full session objects
@override
Future<UserSession?> restoreSession() async {
if (!isAuthenticated) return null;
final response = await client.get<Map<String, dynamic>>('/api/v4/user/me');
return response.data != null ? UserSession.fromJson(response.data!) : null;
}
}
7. Modern OAuth 2.0 & PKCE (NanoOAuth & NanoPkce)
Execute cryptographically secure OAuth 2.0 and OpenID Connect authorization flows (Discord, Google, Apple, GitHub, Auth0, Supabase) with built-in RFC 7636 PKCE, anti-CSRF state, and zero external dependencies:
// 1. Generate a secure PKCE challenge pair + CSRF state token:
final pkce = NanoPkce.generate(
method: NanoPkceMethod.s256,
includeNonce: true, // For OIDC ID Tokens (Apple, Google)
);
// 2. Build the provider's authorization URI:
final authorizationUri = NanoOAuth.buildAuthorizationUri(
authorizationEndpoint: 'https://discord.com/api/oauth2/authorize',
clientId: '123456789',
redirectUri: 'myapp://oauth/callback',
scopes: ['identify', 'email'],
pkce: pkce,
);
// 3. Open browser/WebAuth and parse the callback redirect:
final callbackUrl = await FlutterWebAuth2.authenticate(
url: authorizationUri.toString(),
callbackUrlScheme: 'myapp',
);
final callback = NanoOAuthCallback.fromUrl(callbackUrl);
if (!callback.isSuccess || !callback.isValidState(pkce.state)) {
throw Exception('OAuth verification failed or CSRF state mismatch.');
}
// 4. Exchange authorization code for access/refresh tokens:
final tokenResponse = await dio.post(
'https://discord.com/api/oauth2/token',
data: NanoOAuth.buildAuthorizationCodeBody(
clientId: '123456789',
code: callback.code!,
redirectUri: 'myapp://oauth/callback',
codeVerifier: pkce.codeVerifier,
),
options: Options(headers: {'Content-Type': 'application/x-www-form-urlencoded'}),
);
8. Environment & Build Modes (NanoEnvironment / NanoEnv)
Query compile-time environment flags, automatically detect development vs production releases, and read --dart-define variables:
// Automatic compile-time environment detection:
final isProduction = NanoEnvironment.isProduction; // true in release builds
final isDevelopment = NanoEnvironment.isDevelopment; // true in debug/development builds
// Read strongly-typed compile-time configuration:
final apiUrl = NanoEnvironment.getString('API_URL', defaultValue: 'https://api.example.com');
final customFlag = NanoEnvironment.getBool('FEATURE_ANALYTICS', defaultValue: true);
final timeoutSec = NanoEnvironment.getInt('TIMEOUT_SECONDS', defaultValue: 30);
final threshold = NanoEnvironment.getDouble('THRESHOLD', defaultValue: 1.5);
// Concise alias:
final sameFlag = NanoEnv.getBool('FEATURE_ANALYTICS');
8. Durable Storage vs Expiring Cache (NanoStorage & NanoCache)
NanoStorage: Ideal for permanent key-value persistence without TTL (e.g. auth tokens, user flags viaSharedPreferencesorFlutterSecureStorage).NanoCache: Ideal for volatile HTTP response caching with TTL expiration policies (e.g.HiveorNanoMemoryCache).
๐พ Custom Storage Implementation (e.g., SharedPreferences)
import 'package:nano_core/nano_core.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPrefsStorage implements NanoStorage {
final SharedPreferences prefs;
const SharedPrefsStorage(this.prefs);
@override
T? get<T>(String key) => prefs.get(key) as T?;
@override
void set<T>(String key, T value) {
if (value is String) prefs.setString(key, value);
if (value is bool) prefs.setBool(key, value);
if (value is int) prefs.setInt(key, value);
if (value is double) prefs.setDouble(key, value);
}
@override
void delete(String key) => prefs.remove(key);
@override
void clear({String? prefix}) {
for (final k in prefs.getKeys()) {
if (prefix == null || k.startsWith(prefix)) prefs.remove(k);
}
}
@override
bool has(String key) => prefs.containsKey(key);
}
โก Custom Persistent Cache (e.g., Hive / LocalStorage)
You can persist cached data across app restarts simply by implementing NanoCache:
import 'dart:convert';
import 'package:nano_core/nano_core.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPrefsCache implements NanoCache {
final SharedPreferences prefs;
const SharedPrefsCache(this.prefs);
@override
T? get<T>(String key) {
final raw = prefs.getString(key);
if (raw == null) return null;
return jsonDecode(raw) as T?;
}
@override
void set<T>(String key, T value, {Duration? ttl}) {
prefs.setString(key, jsonEncode(value));
}
@override
void delete(String key) => prefs.remove(key);
@override
void clear({String? prefix}) {
final keys = prefs.getKeys();
for (final k in keys) {
if (prefix == null || k.startsWith(prefix)) {
prefs.remove(k);
}
}
}
@override
bool has(String key) => prefs.containsKey(key);
}
6. Type-Safe Functional Results with NanoResult #
Handle operations with typed business errors without throwing exceptions, using modern Dart 3 sealed class pattern matching:
// 1. Return typed results from UseCases or Services:
Future<NanoResult<User, String>> login(String email, String password) async {
if (password.length < 6) {
return const NanoResult.failure('Password too short');
}
try {
final user = await authApi.authenticate(email, password);
return NanoResult.success(user);
} catch (e) {
return NanoResult.failure('Invalid credentials');
}
}
// 2. Consume with Dart 3 Pattern Matching:
final result = await login('dev@nano.core', 'secret123');
final message = switch (result) {
NanoSuccess(:final data) => 'Welcome back, ${data.name}!',
NanoFailure(:final error) => 'Login failed: $error',
};
// 3. Or wrap any existing async call safely:
final safeResult = await NanoResult.runAsync(() => userRepository.getAll());
7. Reactive Forms, Internationalized Validators & NanoTextField #
Build robust, strongly-typed forms with immutable entities, automatic view state updates via updateForm, and BuildContext i18n support:
1. Define Form Entity & View State
class UserFormEntity extends NanoFormEntity {
const UserFormEntity({
this.name = '',
this.email = '',
});
final String name;
final String email;
UserFormEntity copyWith({
String Function()? name,
String Function()? email,
}) =>
UserFormEntity(
name: name != null ? name() : this.name,
email: email != null ? email() : this.email,
);
@override
List<Object?> get props => [name, email];
}
class RegisterViewState extends NanoFormState<UserFormEntity> {
const RegisterViewState({super.form = const UserFormEntity()});
RegisterViewState copyWith({UserFormEntity? form}) =>
RegisterViewState(form: form ?? this.form);
}
2. Manage via Controller with submit & reset
class RegisterController
extends NanoFormController<RegisterViewState, UserFormEntity> {
final UserRepository userRepository;
RegisterController(this.userRepository)
: super(initialData: const RegisterViewState());
void saveUser() {
// ๐ฏ submit automatically validates all fields before execution:
submit((form) {
execute(() => userRepository.create(form));
});
}
}
3. Render with NanoForm & Reactive NanoTextField in View
NanoForm(
controller: controller,
child: Column(
children: [
NanoTextField(
value: state.data?.form.name,
label: 'Full Name',
prefixIcon: const Icon(Icons.person_outline),
validators: [
NanoValidator.required('Name is required'),
NanoValidator.minLength(3, 'Minimum 3 characters'),
],
autoValidateMode: NanoAutoValidateMode.onUserInteraction,
onChanged: (text) => controller.updateForm(
(s) => s.copyWith(form: s.form.copyWith(name: () => text)),
),
),
const SizedBox(height: 14),
// Built-in Brazilian Document Validation (CPF, CNPJ, or Hybrid):
NanoTextField(
value: state.data?.form.document,
label: 'Document (CPF or CNPJ)',
prefixIcon: const Icon(Icons.badge_outlined),
validators: [
NanoValidator.required('Document is required'),
NanoValidator.cpfOrCnpj('Please provide a valid CPF or CNPJ'),
],
onChanged: (text) => controller.updateForm(
(s) => s.copyWith(form: s.form.copyWith(document: () => text)),
),
),
const SizedBox(height: 20),
FilledButton(
onPressed: controller.saveUser,
child: const Text('Save User'),
),
],
),
)
4. Built-in Native Validators (NanoValidator)
nano_core provides strongly typed, offline-first validators matching Flutter's standard FormFieldValidator<T>:
| Category | Validators |
|---|---|
| Core Form | NanoValidator.required(msg), NanoValidator.email(msg), NanoValidator.minLength(len, msg), NanoValidator.maxLength(len, msg), NanoValidator.min(val, msg), NanoValidator.max(val, msg), NanoValidator.pattern(regex, msg), NanoValidator.match(otherField, msg) |
| Brazilian Documents | NanoValidator.cpf(msg) (standard Modulo 11 check), NanoValidator.cnpj(msg) (supports both legacy numeric and new alphanumeric IN RFB 2.229/2024), NanoValidator.cpfOrCnpj(msg) (auto-detects by length) |
| Cards & Payments | NanoValidator.creditCard(msg) (Luhn algorithm), NanoValidator.creditCardExpiration(msg) (MM/YY or MM/YYYY future date), NanoValidator.creditCardCvv(msg) (3 or 4 digits) |
8. Structured Logging with NanoLogger & NanoLogFilter #
Nano Core provides an enterprise-grade structured console logger with ANSI colors, method tracing, execution timestamps, and granular category filtering via NanoLogFilter.
๐ ๏ธ Initialization & Granular Filtering
Configure logger presets, telemetry hooks, and filters centrally in your main() function:
void main() {
// Central bootstrap configuration:
NanoLogger.init(
// Choose a preset or custom level list:
filter: NanoEnvironment.isDevelopment
? const NanoLogFilter.all()
: const NanoLogFilter.onlyErrors(),
showTimestamp: true,
showColors: true,
maxStackTraceLines: 10,
onError: (entry) {
// Hook errors directly into Firebase Crashlytics, Sentry, or Datadog:
FirebaseCrashlytics.instance.recordError(
entry.error,
entry.stackTrace,
reason: entry.message,
);
},
);
runApp(const MyApp());
}
๐ฏ NanoLogFilter Presets
| Filter Preset | Active Levels | Typical Use Case |
|---|---|---|
NanoLogFilter.all() |
debug, info, success, warning, error, http |
Local Development |
NanoLogFilter.onlyErrors() |
error |
Production / Release Builds |
NanoLogFilter.errorsAndWarnings() |
warning, error |
Staging / QA Builds |
NanoLogFilter.onlyHttp() |
http |
Network & API Traffic Debugging |
NanoLogFilter.none() |
None (completely silent) | Integration & Benchmark Tests |
NanoLogFilter.only([...]) |
Custom selection | Custom debugging workflows |
๐ชต Logging Events
Log formatted, color-coded, and tagged events with method tracking and data inspection:
import 'package:nano_core/nano_core.dart';
// Info with method tracking and data payload:
NanoLogger.info(
'User authenticated successfully',
tag: 'AuthService',
method: 'loginWithEmail',
data: {'userId': '123', 'role': 'admin'},
);
// Success notification (using the short alias NanoLog or NLog):
NanoLog.success('Cache synchronized', tag: 'UserRepository');
NLog.info('Shortest syntax!');
// HTTP event with httpMethod and statusCode:
NLog.http(
'/users',
httpMethod: 'GET',
statusCode: 200,
tag: 'NanoHttp',
method: 'getUsers',
data: {'count': 2},
);
// Error reporting with exception, statusCode and stack trace:
NanoLog.error(
'Failed to fetch user profile',
statusCode: 404,
tag: 'UserRepository',
method: 'getById',
data: {'id': '123'},
error: exception,
stackTrace: stackTrace,
);
// Dynamic runtime filter controls:
NanoLogger.setFilter(const NanoLogFilter.onlyHttp());
NanoLogger.disable(); // or NanoLogger.mute()
NanoLogger.enable(); // or NanoLogger.unmute()
Tip: You can use
NanoLogger,NanoLog, orNLoginterchangeably as concise aliases.
9. Telemetry & Observability (NanoTelemetry, NanoAnalyticsObserver & NanoCrashObserver) #
nano_core includes a 100% decoupled, zero-dependency Telemetry & Observability architecture. The framework remains pure Dart/Flutter without coupling to any third-party SDK.
Your application plugs into monitoring and analytics tools (Firebase Analytics, Firebase Crashlytics, Sentry, Datadog, Mixpanel) simply by implementing two pure observer contracts.
๐งฉ 1. The Pure Contracts
NanoAnalyticsObserver
abstract interface class NanoAnalyticsObserver {
void onScreenView(String screenName, {Map<String, dynamic>? parameters});
void onEvent(String name, {Map<String, dynamic>? parameters});
void setUserId(String? id);
void setUserProperty(String key, String value);
}
NanoCrashObserver
abstract interface class NanoCrashObserver {
void recordError(
dynamic error,
StackTrace? stackTrace, {
dynamic reason,
bool fatal = false,
});
void log(String message); // Diagnostic Breadcrumbs
void setCustomKey(String key, Object value);
void setUserId(String? id);
}
๐ 2. Implementing Observers (Example: Firebase)
In your client application, implement the contracts using your chosen SDKs:
// 1. Analytics Observer (Firebase Analytics):
class MyFirebaseAnalyticsObserver implements NanoAnalyticsObserver {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
@override
void onScreenView(String screenName, {Map<String, dynamic>? parameters}) {
_analytics.logScreenView(screenName: screenName, parameters: parameters);
}
@override
void onEvent(String name, {Map<String, dynamic>? parameters}) {
_analytics.logEvent(name: name, parameters: parameters);
}
@override
void setUserId(String? id) => _analytics.setUserId(id: id);
@override
void setUserProperty(String key, String value) {
_analytics.setUserProperty(name: key, value: value);
}
}
// 2. Crash Observer (Firebase Crashlytics):
class MyFirebaseCrashObserver implements NanoCrashObserver {
final FirebaseCrashlytics _crashlytics = FirebaseCrashlytics.instance;
@override
void recordError(
dynamic error,
StackTrace? stackTrace, {
dynamic reason,
bool fatal = false,
}) {
_crashlytics.recordError(
error,
stackTrace,
reason: reason,
fatal: fatal,
);
}
@override
void log(String message) => _crashlytics.log(message);
@override
void setCustomKey(String key, Object value) {
_crashlytics.setCustomKey(key, value);
}
@override
void setUserId(String? id) => _crashlytics.setUserIdentifier(id ?? '');
}
๐ 3. Centralized Injections (NanoDefaultInjections)
Register your observers once during app bootstrap. NanoDefaultInjections registers NanoTelemetry into GetIt:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
NanoDefaultInjections.init(
GetIt.I,
analyticsObservers: [
MyFirebaseAnalyticsObserver(),
// You can pass multiple tools simultaneously (e.g. Mixpanel, Datadog)!
],
crashObservers: [
MyFirebaseCrashObserver(),
// e.g. SentryCrashObserver()
],
);
runApp(const MyApp());
}
โก 4. Automatic Screen Tracking & Crash Wiring
When telemetry observers are configured, the framework handles the heavy lifting automatically:
- Anti-Cardinality Screen Tracking:
NanoRouterandNanoRouteObserverautomatically trackPageRoutescreen views. Canonical route templates (e.g.,"/product/:id") are sent asscreenNamewhile dynamic values go intoparameters, preventing dashboard fragmentation. - Automatic Flutter & Platform Crash Wiring:
NanoAppautomatically connectsFlutterError.onErrorandPlatformDispatcher.instance.onErrortoNanoTelemetry.recordError(..., fatal: true). - Smart Repository Telemetry:
NanoRepositoryautomatically catches real JSON parsing/adapter exceptions (TypeError,FormatException) and reports them as bugs viarecordError, while operational network failures (offline, 401, timeouts) are recorded as non-polluting diagnostic breadcrumbs (NanoTelemetry.log).
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return NanoApp(
router: myNanoRouter,
// NanoApp automatically wires global crashes and injects NanoRouteObserver!
);
}
}
๐ฏ 5. Manual Event Tracking & Error Logging
In Controllers / Services:
// Custom business event:
NanoTelemetry.onEvent('checkout_completed', parameters: {
'order_id': 'ORD-9821',
'total': 250.0,
});
// Identify user on login / logout:
NanoTelemetry.setUserId('user_42');
NanoTelemetry.setUserId(null); // On logout
In Try / Catch:
try {
await paymentService.charge();
} catch (e, s) {
// 1-line catch ergonomics:
// 1. Dispatches to all registered NanoCrashObservers
// 2. Formats and prints locally via NanoLogger.error (default debugPrint: true)
NanoTelemetry.recordError(
e,
s,
reason: 'Payment transaction failed',
);
}
6. Universal State Management (BLoC, Cubit, MobX, GetX, Signals) #
NanoScaffold can observe any external state management library via the lightweight NanoStateObservable contract or using out-of-the-box generic adapters:
โก Option A: Out-of-the-Box Generic Adapters (Zero Boilerplate)
// 1. Any Stream (BLoC, Cubit, RxDart, WebSockets):
final blocController = NanoStreamAdapter<UserState, BlocState>(
stream: userBloc.stream,
initialState: InitialState(),
mapper: (blocState) => switch (blocState) {
UserLoading() => LoadingState(),
UserSuccess(:final user) => SuccessState(data: user),
_ => InitialState(),
},
);
// 2. Any Listenable (MobX, Signals, ValueNotifier, Provider):
final storeController = NanoListenableAdapter<UserState>(
listenable: userStore,
stateGetter: () => userStore.isBusy
? LoadingState()
: SuccessState(data: userStore.user),
);
// Use directly in NanoScaffold with reactive listener, toasts & fallback messages:
NanoScaffold(
controller: blocController,
defaultErrorMessage: 'An unexpected error occurred', // Fallback for ErrorState(key: null)
defaultWarningMessage: 'Please review your input', // Fallback for WarningState(key: null)
listener: (context, state) {
if (state is SuccessState) {
// Execute one-time side-effects (navigation, dialogs, analytics)
}
},
builder: (context, state) => Text('User: ${state.data?.name}'),
);
Note
Notification & Feedback Priority Order (ErrorState / WarningState):
- Custom Hook: If
onCustomErrororonCustomWarningis provided, it is invoked and bypasses automatic toasts. - Typed Key Message: If
key?.message(context)produces a non-empty string, it is displayed viaNanoToast. - Fallback Default: If
keyisnullor produces an empty string,defaultErrorMessageordefaultWarningMessageis displayed if non-empty. - Silent (No-op): If no message is found, no empty toast is displayed, ensuring zero visual bugs.
๐ ๏ธ Option B: Custom Class Implementation
1. BLoC / Cubit Class Adapter
class UserCubitAdapter extends ChangeNotifier
implements NanoStateObservable<UserState> {
final UserCubit cubit;
late final StreamSubscription _sub;
UserCubitAdapter(this.cubit) {
_sub = cubit.stream.listen((_) => notifyListeners());
}
@override
NanoState<UserState> get state => switch (cubit.state) {
UserLoading() => LoadingState(),
UserSuccess(:final user) => SuccessState(data: user),
UserError() => ErrorState(),
_ => InitialState(),
};
@override
void dispose() {
_sub.cancel();
super.dispose();
}
}
2. MobX Class Adapter
class UserMobxAdapter extends ChangeNotifier
implements NanoStateObservable<UserState> {
final UserStore store;
late final ReactionDisposer _disposer;
UserMobxAdapter(this.store) {
_disposer = autorun((_) => notifyListeners());
}
@override
NanoState<UserState> get state {
if (store.isLoading) return LoadingState();
if (store.user != null) return SuccessState(data: store.user!);
return InitialState();
}
@override
void dispose() {
_disposer();
super.dispose();
}
}
3. GetX Class Adapter
class UserGetxAdapter extends ChangeNotifier
implements NanoStateObservable<UserState> {
final UserController getxController;
late final Worker _worker;
UserGetxAdapter(this.getxController) {
_worker = ever(getxController.stateRx, (_) => notifyListeners());
}
@override
NanoState<UserState> get state => getxController.stateRx.value;
@override
void dispose() {
_worker.dispose();
super.dispose();
}
}
4. Signals / ValueNotifier Class Adapter
class UserSignalsAdapter extends ChangeNotifier
implements NanoStateObservable<UserState> {
final Signal<NanoState<UserState>> signalState;
late final VoidCallback _cleanup;
UserSignalsAdapter(this.signalState) {
_cleanup = effect(() {
signalState.value; // register dependency
notifyListeners();
});
}
@override
NanoState<UserState> get state => signalState.value;
@override
void dispose() {
_cleanup();
super.dispose();
}
}
9. Debounced Search Inputs #
Delay expensive operations or search API calls until the user pauses typing:
// Native integration with NanoTextField:
NanoTextField(
label: 'Search products...',
prefixIcon: const Icon(Icons.search),
debounceDuration: const Duration(milliseconds: 400),
onChanged: (query) => controller.search(query),
)
// Or using standalone NanoDebouncer:
final debouncer = NanoDebouncer(duration: const Duration(milliseconds: 300));
debouncer.run(() => fetchSearchResults(query));
10. Reactive Connectivity & Offline Handling #
Monitor network connectivity state with zero external dependencies:
// 1. Register in NanoDefaultInjections:
NanoDefaultInjections.register(
connectivity: NanoConnectivity(),
);
// 2. Observe in NanoScaffold with custom connectivityBuilder:
NanoScaffold<ProductsState, ProductsMessages>(
controller: controller,
connectivityBuilder: (context, status) => switch (status) {
NanoConnectivityStatus.none => Container(
color: Colors.red.withValues(alpha: 0.9),
padding: const EdgeInsets.all(8),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.wifi_off, color: Colors.white, size: 18),
SizedBox(width: 8),
Text(
'No internet connection',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
),
_ => null,
},
builder: (context, state) => ...,
)
11. Encapsulated Commands (NanoCommand & NanoCommandBuilder) #
Encapsulate individual user actions and async operations into reactive commands with granular button loading indicators:
// 1. Define commands inside your controller using nanoCommand0 / nanoCommand1:
class LoginController extends NanoController<LoginViewState> {
LoginController(this.repository);
final AuthRepository repository;
// Parameterless command with auto-dispose:
late final refreshCommand = nanoCommand0<void>(
repository.fetchProfile,
);
// Single-argument command with declarative success callback & auto-dispose:
late final signInCommand = nanoCommand1<String, bool>(
repository.signIn,
onSuccess: (success) {
emit(SuccessState(state.data?.copyWith(isAuthenticated: success)));
},
onError: (error) {
// Custom error handling
},
);
@override
Future<void> init(String? id) async {}
void handleSignIn(String provider) => signInCommand.run(provider);
}
// 2. Reactively bind individual buttons in UI:
NanoCommandBuilder<bool>(
command: controller.signInCommand,
builder: (context, cmdState) => ElevatedButton(
onPressed: cmdState is LoadingState
? null
: () => controller.signInCommand.run('google'),
child: cmdState is LoadingState
? const CircularProgressIndicator()
: const Text('Sign in with Google'),
),
)
12. Model Adapters (NanoReadAdapter, NanoWriteAdapter & NanoAdapter) #
Segregated serialization and deserialization contracts following the Interface Segregation Principle, complete with built-in safe list handling (fromList and toList):
// 1. Read-Only Adapter (API responses):
class ProductAdapter extends NanoReadAdapter<Product> {
const ProductAdapter();
@override
Product fromJson(Map<String, dynamic> json) => Product(
id: json['id'] as String,
name: json['name'] as String,
);
}
// Safely parse nested lists from API JSON with zero manual casting:
final products = const ProductAdapter().fromList(json['products']);
// 2. Write-Only / Query Adapter (POST/PUT request payloads & URL query parameters):
class CreateOrderAdapter extends NanoWriteAdapter<OrderDraft> {
const CreateOrderAdapter();
static const _itemAdapter = OrderItemAdapter();
@override
Map<String, dynamic> toMap(OrderDraft draft) => {
'customerId': draft.customerId,
// Safely serialize nested list of entities to List<Map<String, dynamic>>:
'items': _itemAdapter.toList(draft.items),
};
}
// 3. Bidirectional Adapter (Full CRUD entities):
class UserAdapter extends NanoAdapter<User> {
const UserAdapter();
@override
User fromMap(Map<String, dynamic> map) => User(
id: map['id'] as String,
name: map['name'] as String,
);
@override
Map<String, dynamic> toMap(User user) => {
'id': user.id,
'name': user.name,
};
}
13. Dependency Injection (NanoInjections, Async Binds & NanoDefaultInjections) #
Manage scoped dependency lifecycles with GetIt supporting both synchronous and asynchronous bindings without manual scope management:
// 1. Root Application Injections (Supports async bindings with `Future<void> binds`):
class AppInjections extends NanoInjections {
const AppInjections({super.scope = 'app_global'});
@override
Future<void> binds(GetIt i) async {
final prefs = await SharedPreferences.getInstance();
final cache = await AppHiveCache.init();
final storage = AppSharedPreferencesStorage(prefs);
final config = AppEnvironments.current;
i.registerSingleton<AppConfig>(config);
final client = AppDioHttpClient(
baseUrl: config.apiBaseUrl,
interceptors: [
const NanoAuthInterceptor(),
if (config.logEnabled) const NanoHttpLogInterceptor(),
],
);
// Initialize default framework services:
NanoDefaultInjections.init(
i,
client: client,
storage: storage,
cache: cache,
);
}
}
// 2. Minimalist Main Bootstrap:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await const AppInjections()();
runApp(const App());
}
// 3. Feature/Module Injections (Synchronous bindings):
class LoginInjections extends NanoInjections {
const LoginInjections({super.scope = 'login'});
@override
void binds(GetIt i) {
i
..registerLazySingleton<AuthRepository>(() => AuthRepository())
..registerFactory<LoginController>(
() => LoginController(i<AuthRepository>()),
);
}
}
14. Native Skeleton Loading & Wave Shimmer (NanoSkeleton & NanoShimmer) #
nano_core provides a 100% native, zero-dependency skeleton loading design system and GPU-accelerated wave gradient shimmer animation widget powered by Flutter's built-in ShaderMask and AnimatedBuilder.
1. Ready-to-Use Layout Presets
Quickly render complete layout placeholders without building ad-hoc loading widgets:
// 1. Shimmering List of avatar and text lines:
NanoShimmer(
child: NanoSkeleton.list(items: 5, spacing: 16),
)
// 2. Structural Card skeleton:
NanoShimmer(
child: NanoSkeleton.card(height: 140),
)
// 3. Responsive Grid of skeleton tiles:
NanoShimmer(
child: NanoSkeleton.grid(
columns: 2,
rows: 3,
itemHeight: 100,
spacing: 12,
),
)
2. Geometric Building Blocks (Primitives)
Assemble custom loading skeletons using elementary shapes:
NanoShimmer(
child: Row(
children: [
NanoSkeleton.circle(size: 56),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NanoSkeleton.text(lines: 2, lineSpacing: 6),
const SizedBox(height: 8),
NanoSkeleton.box(width: 120, height: 14),
],
),
),
],
),
)
3. GPU-Accelerated Ghost Masking (NanoSkeleton.mask)
Wrap any existing widget tree with .mask(). When loading: true, it automatically converts all visual child shapes into shimmering silhouettes while preserving the exact layout geometry:
NanoSkeleton.mask(
loading: state is LoadingState,
child: UserProfileCard(user: state.data?.user),
)
4. Directional Wave Shimmer (NanoShimmerDirection)
Customize wave motion direction (ltr, rtl, ttb, btt), duration, base and highlight colors:
NanoShimmer(
direction: NanoShimmerDirection.rtl, // Right-to-Left wave
duration: const Duration(milliseconds: 1200),
baseColor: Colors.grey.shade800,
highlightColor: Colors.grey.shade600,
child: NanoSkeleton.card(),
)
5. Native Integration with NanoPaginatedListView & NanoScaffold
NanoPaginatedListView natively defaults to NanoSkeleton.list() as its initial loading placeholder:
// NanoPaginatedListView automatically displays shimmering skeleton rows during initial fetch:
NanoPaginatedListView<User>(
paginator: controller.paginator,
itemBuilder: (context, user, index) => UserTile(user: user),
// Optional custom loading override:
loadingWidget: const NanoLoadingOverlay(),
)
// NanoScaffold page loading slot:
NanoScaffold<UserState, UserMessages>(
controller: controller,
loadingWidget: Padding(
padding: const EdgeInsets.all(16),
child: NanoSkeleton.list(items: 6),
),
builder: (context, state) => ...,
)
๐ Supporting & Sponsoring #
nano_core is an open-source framework created to elevate architecture, performance, and developer experience in Flutter multiplatform applications. If this framework saved you time or is helping your team, consider supporting its continuous development:
- โญ Star the Project: Give us a star on GitHub to help more developers discover the project!
- โ Buy Me a Coffee: Support development via Buy Me a Coffee
- ๐ PIX (Brazil):
durvalperipatoneto@gmail.com
๐ฌ Community, Support & Feedback #
- ๐ Issue Tracker: GitHub Issues
- โ๏ธ Direct Contact: durvalana8893@gmail.com
- ๐ Website: nanodevs.com.br
- ๐ผ Author: Durval Peripato Neto
License #
This project is licensed under the MIT License - see the LICENSE file for details.