nano_core 1.0.5 copy "nano_core: ^1.0.5" to clipboard
nano_core: ^1.0.5 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 Components: Standalone reusable UI widgets such as NanoLoadingOverlay, NanoToast, NanoPaginatedListView, NanoPaginationBar, NanoTextField, and NanoPoweredBy.

  • πŸ–₯️ 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.5

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((context) => 'Name is required'),
          NanoValidator.minLength(3, (context) => 'Minimum 3 characters'),
        ],
        autoValidateMode: NanoAutoValidateMode.onUserInteraction,
        onChanged: (text) => controller.updateForm(
          (s) => s.copyWith(form: s.form.copyWith(name: () => text)),
        ),
      ),
      const SizedBox(height: 20),
      FilledButton(
        onPressed: controller.saveUser,
        child: const Text('Save User'),
      ),
    ],
  ),
)

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>()),
      );
  }
}

πŸ’– 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
911
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