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

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

Nano Core #

Pub Version License: MIT Status: Stable

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

Features #

  • 📱 NanoApp: Zero-boilerplate root application widget automatically configuring NanoRouter, MaterialApp, themes, and localizations.

  • 🧭 NanoRouter & Declarative Routes: Intuitive zero-dependency declarative router supporting public routes (NanoRoute), custom animated transitions (NanoAnimatedRoute), route groups (NanoGroupRoute), typed sub-routes (NanoDetailsRoute<Args>), access-guarded routes (NanoProtectedRoute), and redirects (NanoRedirectRoute).

  • 🔭 NanoRouteObserver: Granular navigation observer for screen tracking, Firebase Analytics, Datadog, breadcrumbs, and route lifecycle telemetry.

  • 🚀 NanoScaffold & NanoStateObservable: Decoupled reactive base page scaffold supporting Web/Desktop headers, mobile AppBars, loading overlays, toasts, and universal state observation (NanoController, BLoC, Cubit, MobX adapters).

  • NanoController & NanoState: Clean, reactive state management built on ChangeNotifier and ListenableBuilder.

  • 📊 NanoViewState: Base class for structured, immutable and equatable view/page state data models.

  • 🛠️ NanoCommand & NanoCommandBuilder: Encapsulated async commands for user actions and operations.

  • 🌐 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.

  • 📄 Pagination & NanoPaginator: Pluggable strategies (NanoOffsetPagination, NanoCursorPagination), reactive controller (NanoPaginator), automatic infinite scrolling widget (NanoPaginatedListView), and customizable navigation bar (NanoPaginationBar).

  • NanoCache & Smart Caching: Zero-dependency in-memory caching (NanoMemoryCache) with configurable policies (cacheFirst, networkFirst, networkOnly, cacheOnly), TTL expiration, and automatic invalidation on CRUD mutations.

  • 🛡️ 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.

  • 🏷️ NanoEntity & NanoEquatable: Base domain entity with unique identification and value-based equality.

  • 🪵 NanoLogger: Central structured console logger with ANSI styling, severity levels (debug, info, success, warning, error, http), method context tracking, data payloads, and telemetry hooks.

  • 💉 NanoInjections, NanoDefaultInjections & NanoStatePage: Dependency injection scoping with GetIt, default framework services registration (NanoDefaultInjections.init), modular composition, and page lifecycle binding.

  • ⏱️ NanoDebouncer: Flexible async execution delay for search inputs, autocomplete, and live filters with native NanoTextField(debounceDuration: ...) support.

  • 🌐 NanoConnectivity: Zero-dependency cross-platform reactive network monitor (NanoConnectivity, NanoConnectivityStatus) with seamless NanoScaffold(connectivityBuilder: ...) integration.

  • 🧩 Design System Components: Standalone reusable UI widgets such as NanoLoadingOverlay, NanoToast, NanoPaginatedListView, NanoPaginationBar, and NanoTextField.

  • 🖥️ NanoDeviceType: Real-time cross-platform environment and responsive viewport width inspection.

Getting Started #

Add nano_core to your pubspec.yaml:

dependencies:
  nano_core: ^0.5.0

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 fromJson(Map<String, dynamic> json) => User(
    id: json['id'] as String,
    name: json['name'] as String,
  );

  @override
  Map<String, dynamic> toJson(User user) => {
    'id': user.id,
    'name': user.name,
  };
}

class UserRepository extends NanoRepository<User, String> {
  UserRepository([super.client])
      : super(
          endpoint: '/users',
          adapter: const UserAdapter(),
        );
}

// Type-Safe Search with NanoSearchRepository:
class UserFilter {
  final String? role;
  final int page;
  const UserFilter({this.role, this.page = 1});
}

class UserFilterAdapter extends NanoQueryAdapter<UserFilter> {
  const UserFilterAdapter();

  @override
  Map<String, dynamic> toQueryParams(UserFilter query) => {
    'page': query.page,
    if (query.role != null) 'role': query.role,
  };
}

class UserSearchRepository
    extends NanoSearchRepository<User, String, UserFilter> {
  UserSearchRepository([super.client])
      : super(
          endpoint: '/users',
          adapter: const UserAdapter(),
          queryAdapter: const UserFilterAdapter(),
        );
}

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

  @override
  Future<void> init(String? id) async {
    await loadUsers();
  }

  Future<void> loadUsers() async {
    execute(() async {
      final users = await repository.getAll();
      return UsersState(users: users);
    });
  }
}

// 3. Page Injections Scope
class UsersInjections extends NanoInjections {
  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 => UsersInjections();

  @override
  Widget build(BuildContext context) {
    return NanoScaffold<UsersState, NanoMessageKey>(
      controller: controller,
      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 implements NanoHttpClient {
  final Dio _dio;

  DioHttpClient(this._dio);

  @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. 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 implements NanoQueryAdapter<UserFilter> {
  const UserFilterAdapter();

  @override
  Map<String, dynamic> toQueryParams(UserFilter query) => {
    if (query.name != null && query.name!.isNotEmpty) 'name': query.name,
    if (query.role != null && query.role != 'all') 'role': query.role,
  };
}

2. Create Search Repository

class UserRepository extends NanoSearchRepository<User, String, UserFilter> {
  UserRepository([super.client])
      : super(
          endpoint: '/users',
          adapter: const UserAdapter(),
          queryAdapter: const UserFilterAdapter(),
        );
}

3. 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);
💾 Custom Persistent Cache (e.g., SharedPreferences / 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 #

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

Tip: You can use NanoLogger, NanoLog, or NLog interchangeably as concise aliases.

Hook errors directly into Crashlytics or Sentry:

NanoLogger.onError = (entry) {
  FirebaseCrashlytics.instance.recordError(
    entry.error,
    entry.stackTrace,
    reason: entry.message,
  );
};

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:
NanoScaffold(
  controller: blocController,
  builder: (context, state) => Text('User: ${state.data?.name}'),
);

🛠️ 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) => ...,
)

License #

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

5
likes
0
points
1.22k
downloads

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 #routing #scaffold

License

unknown (license)

Dependencies

cupertino_icons, flutter, get_it

More

Packages that depend on nano_core