nano_core 1.0.6 copy "nano_core: ^1.0.6" to clipboard
nano_core: ^1.0.6 copied to clipboard

A lightweight reactive architecture framework and design system toolkit for Flutter multiplatform applications.

Nano Core #

Pub Version License: MIT MCP Protocol Buy Me A Coffee Status: Stable

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 #

nano-core-mcp Architecture

๐ŸŒ 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 ChangeNotifier and ListenableBuilder.

  • ๐Ÿ“Š 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 class hierarchy (NanoSuccess, NanoFailure) with compile-time pattern matching, fold, map, and runAsync safe execution.

  • ๐Ÿ“ NanoForm & Validators: Strongly-typed form models, automatic field disposal, BuildContext i18n support, and reactive NanoTextField component.


๐Ÿ” 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 seamless NanoScaffold(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, and NanoPoweredBy.

  • โœจ 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-define parsers, 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,
    );
  }
}
// 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:

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 via SharedPreferences or FlutterSecureStorage).
  • NanoCache: Ideal for volatile HTTP response caching with TTL expiration policies (e.g. Hive or NanoMemoryCache).
๐Ÿ’พ 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, or NLog interchangeably 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:

  1. Anti-Cardinality Screen Tracking: NanoRouter and NanoRouteObserver automatically track PageRoute screen views. Canonical route templates (e.g., "/product/:id") are sent as screenName while dynamic values go into parameters, preventing dashboard fragmentation.
  2. Automatic Flutter & Platform Crash Wiring: NanoApp automatically connects FlutterError.onError and PlatformDispatcher.instance.onError to NanoTelemetry.recordError(..., fatal: true).
  3. Smart Repository Telemetry: NanoRepository automatically catches real JSON parsing/adapter exceptions (TypeError, FormatException) and reports them as bugs via recordError, 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):

  1. Custom Hook: If onCustomError or onCustomWarning is provided, it is invoked and bypasses automatic toasts.
  2. Typed Key Message: If key?.message(context) produces a non-empty string, it is displayed via NanoToast.
  3. Fallback Default: If key is null or produces an empty string, defaultErrorMessage or defaultWarningMessage is displayed if non-empty.
  4. 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 #


License #

This project is licensed under the MIT License - see the LICENSE file for details.

5
likes
160
points
1.02k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight reactive architecture framework and design system toolkit for Flutter multiplatform applications.

Homepage
Repository (GitHub)
View/report issues

Topics

#architecture #state-management #design-system #mcp #ai

License

MIT (license)

Dependencies

cupertino_icons, flutter, get_it

More

Packages that depend on nano_core

Packages that implement nano_core