跪拜 Guibai
← Back to the summary

FlutterKit Ships a GetX Scaffold With 98% Test Coverage and AI-Ready Structure

Out-of-the-box Flutter Universal Scaffold — FlutterKit

In an era where AI-assisted development is becoming routine, a scaffold must not only reduce repetitive work but also allow AI to understand the project structure, follow development conventions, and complete development within clear architectural boundaries.

Previously, I shared rapid development frameworks for HarmonyOS and Android. This time, I'm bringing the Flutter version. FlutterKit is based on Flutter + Dart + GetX + TDesign Flutter, with built-in capabilities for networking, pagination, database, state management, navigation, screen adaptation, and more. It supports dark mode, internationalization, and multi-device adaptation. Welcome to learn and exchange ideas together.

The project also provides a project-level AGENTS.md and specialized Skills covering UI, data, navigation, theming, previews, and other scenarios, helping AI participate in feature development, code refactoring, unit testing, and documentation maintenance.

Project Highlights

If this project helps you, please give a Star to support it ⭐ It's very important to me and provides the motivation for long-term updates and maintenance!

Project Address: GitHub | Gitee
Online Documentation: https://flutter.dusksnow.top
Demo Download: https://www.pgyer.com/FlutterKit

Project Preview

💡 Note: The framework provides various example pages demonstrating networking, pagination, database, theming, navigation, state management, and other capabilities, supporting device forms such as phones, foldables, and tablets.

📱 Phone

mobile.png

📱 Tablet

tablet.png

Architecture Design

FlutterKit adopts a modular layered architecture, divided from top to bottom into the business layer, core layer, and entry layer:

Business Layer (Feature)

Core Layer (Core)

Entry Layer (Application)

Dependency Rules

architecture.png

Tech Stack

Category Technology Selection Description
Language Dart Flutter's official development language
UI Framework Flutter + TDesign Flutter Cross-platform UI and business components
Architecture View + Logic + State + Binding Page layering and dependency registration
State & Nav GetX Rx, Obx, GetPage, Binding, Service
Networking Dio + Retrofit HTTP client, interceptors, and interface declarations
Data Storage sqflite + shared_preferences Database and lightweight local storage
Screen Adapter flutter_screenutil + BreakpointType Size adaptation and responsive breakpoints

Core Capabilities

1. Network Request Encapsulation

Network request encapsulation based on Dio + Retrofit, adopting a DataSource → Repository → Logic three-layer architecture. The framework uniformly handles errors, loading, toasts, dialogs, and page states.

Features:

Usage Example:

/// Request product details.
Future<void> requestGoodsDetail() {
  return RequestHelper.repository<Goods>(
    GoodsRepository().getGoodsInfo(1),
  )
      .loading(true) // Show Loading during the request.
      .toast(true) // Automatically show Toast on business failure.
      .execute()
      .then<void>((Goods? goods) {
        LogUtil.d('Product details: ${goods?.title}');
      })
      .catchError((Object error) {
        LogUtil.e(error.toString());
      });
}

Single Request Scenario (automatically handles loading, error, empty data, and success states):

/// Network demo page State.
class NetworkDemoState extends BaseNetworkState {
  /// Product details.
  final Rx<Goods> goods = Goods().obs;
}

/// Network demo page Logic.
class NetworkDemoLogic extends BaseNetworkLogic<Goods> {
  /// Page display state.
  final NetworkDemoState networkDemoState = NetworkDemoState();

  /// Product repository.
  final GoodsRepository _goodsRepository = GoodsRepository();

  /// Network parent class reuses the State declared by the page.
  @override
  NetworkDemoState get networkState => networkDemoState;

  /// Product details request.
  @override
  Future<BaseResponse<Goods>> Function()? get apiRequest =>
      () => _goodsRepository.getGoodsInfo(1);

  /// Save product details.
  @override
  void requestOk(Goods data) => networkDemoState.goods.value = data;
}
/// Network demo page.
class NetworkDemoView extends BaseNetworkView<NetworkDemoLogic> {
  /// Create network demo page.
  NetworkDemoView({super.key, super.logic});

  /// Build page content after a successful request.
  @override
  Widget bodyContent(NetworkDemoLogic logic) {
    return Obx(
      () => GoodsDetailContent(
        goods: logic.networkDemoState.goods.value,
      ),
    );
  }
}

BaseNetworkLogic calls apiRequest, and executes requestOk() after a successful request; BaseNetworkView displays Loading, error page, empty page, or business content based on the current state.

For specific implementation code, please refer to: Network Request Documentation | Result Handling Documentation

2. Paginated List Encapsulation

Unified encapsulation of initial loading, pull-to-refresh, load-more, empty state, error handling, and no-more-data for paginated lists. Developers only need to care about request parameters and list item rendering.

Features:

Logic Implementation:

/// Network list demo page State.
class NetworkListDemoState extends BaseListState<Goods> {
  /// Number of products per page.
  @override
  int get pageSize => 15;
}

/// Network list demo page Logic.
class NetworkListDemoLogic extends BaseListLogic<Goods> {
  /// Create network list demo page Logic.
  ///
  /// [goodsRepository] Optional product repository, can inject Fake implementation for testing and previews.
  NetworkListDemoLogic({GoodsRepository? goodsRepository})
      : _goodsRepository = goodsRepository ?? GoodsRepository();

  /// Page pagination state.
  final NetworkListDemoState networkListDemoState = NetworkListDemoState();

  /// Pagination parent class reuses the State declared by the page.
  @override
  NetworkListDemoState get listState => networkListDemoState;

  /// Product repository.
  final GoodsRepository _goodsRepository;

  /// Product pagination request.
  @override
  Future<BaseResponse<BaseListResponse<Goods>>> Function()? get apiRequest {
    return () => _goodsRepository.getGoodsPage(
      GoodsSearchRequest(
        page: listState.currentPage,
        size: listState.pageSize,
      ),
    );
  }
}

Page Usage:

/// Network list demo page.
class NetworkListDemoView
    extends BaseListView<NetworkListDemoLogic, Goods> {
  /// Create network list demo page.
  NetworkListDemoView({super.key, super.logic});

  /// Build product list item.
  ///
  /// [item] Current product.
  /// [index] Current index.
  @override
  Widget itemWidget(Goods item, int index) {
    return GoodsListCard(goods: item);
  }
}

Usage Steps:

  1. State inherits BaseListState<T>, optionally override pageSize.
  2. Logic inherits BaseListLogic<T> and implements apiRequest.
  3. View inherits BaseListView<Logic, T> and implements itemWidget.
  4. Parent class uniformly manages EasyRefresh, page number, list state, and no-more-data.

For specific implementation code, please refer to: Paginated List Documentation

3. State Management

Implements page state and global state sharing based on GetX's Rx, Obx, and GetxService.

Features:

State Definition:

/// Demo counter service.
class DemoCounterService extends GetxService {
  /// Current count value.
  final RxInt count = 0.obs;

  /// Increment count by one.
  void increase() {
    count.value += 1;
  }
}

Usage Example:

/// Build state management example.
Widget buildCounter() {
  final DemoCounterService service = Get.find<DemoCounterService>();

  return Obx(() {
    return Column(
      children: <Widget>[
        TDText('Current count: ${service.count.value}'),
        TDButton(text: 'Increase', onTap: service.increase),
      ],
    );
  });
}

Temporary page state goes in State, cross-page state goes in Service, and application-level state like theme and language goes in Application. It is not recommended to put all Rx into global Services.

For specific implementation code, please refer to: State Management Documentation

4. Navigation Management

Unified management of route registration, page navigation, parameter passing, result return, and login interception.

Features:

Route Definition:

/// Demo module routes.
abstract final class DemoRoutes {
  static const String navigationWithArgs = '/demo/navigation-with-args';
  static const String navigationResult = '/demo/navigation-result';
}

/// Demo module navigation parameters.
class DemoParams {
  const DemoParams({required this.goodsId});
  final int goodsId;
}

/// Demo module navigation results.
class DemoResult {
  const DemoResult({required this.id, required this.message});
  final int id;
  final String message;
}

Navigator Encapsulation (Recommended):

/// Demo module navigator.
abstract final class DemoNavigator {
  /// Navigate to the navigation-with-args demo page.
  static Future<T?>? toNavigationWithArgs<T>(int goodsId) {
    return toPage<T>(
      DemoRoutes.navigationWithArgs,
      arguments: DemoParams(goodsId: goodsId),
    );
  }

  /// Navigate to the result-return demo page.
  static Future<DemoResult?>? toNavigationResult() {
    return toPage<DemoResult>(DemoRoutes.navigationResult);
  }
}

Usage Example:

/// Open page with arguments.
Future<void> openGoodsPage() async {
  await DemoNavigator.toNavigationWithArgs<void>(10086);
}

/// Open result-return page.
Future<void> openResultPage() async {
  final DemoResult? result = await DemoNavigator.toNavigationResult();
  if (result != null) {
    ToastUtil.success(result.message);
  }
}
/// Read current page parameters.
class NavigationWithArgsLogic extends BaseLogic {
  late final DemoParams params = args as DemoParams;
}

/// Return page result.
void submitResult() {
  back<DemoResult>(const DemoResult(id: 1, message: 'Operation successful'));
}

The application root node uses Transition.native, and Android Manifest explicitly enables android:enableOnBackInvokedCallback="true", allowing Android 14+ pages to access predictive back.

For specific implementation code, please refer to: Navigation Management Documentation

5. Screen Adaptation

A complete screen adaptation solution supporting phones, foldables, tablets, and landscape/portrait layouts.

Features:

Breakpoint Rules:

Breakpoint Description Width Range (dp)
XS Extra Small < 320
SM Small 320 - 599
MD Medium 600 - 839
LG Large >= 840

Grid Layout Adaptation:

/// Build responsive product grid.
Widget buildGoodsGrid(BuildContext context, List<Goods> goods) {
  final int columns = context.bp<int>(xs: 2, sm: 2, md: 3, lg: 4);

  return GridView.builder(
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: columns,
      crossAxisSpacing: 12,
      mainAxisSpacing: 12,
    ),
    itemCount: goods.length,
    itemBuilder: (BuildContext context, int index) {
      return GoodsListCard(goods: goods[index]);
    },
  );
}

Text Adaptation:

/// Build responsive text.
Widget buildAdaptiveText(BuildContext context) {
  final double fontSize = context.bp<double>(
    xs: 14,
    sm: 14,
    md: 16,
    lg: 22,
  );

  return Text(
    'Large screen adaptive sample text',
    style: TextStyle(fontSize: fontSize),
  );
}

Navigation Bar Position Adjustment:

/// Switch navigation position based on screen orientation.
Widget buildMainLayout(
  BuildContext context,
  Widget page,
  Widget sideNavigation,
) {
  final bool isLandscape =
      MediaQuery.orientationOf(context) == Orientation.landscape;

  if (isLandscape) {
    return Row(
      children: <Widget>[
        sideNavigation,
        Expanded(child: page),
      ],
    );
  }

  return page;
}

Safe Area Adaptation:

Normal pages avoid the status bar via BaseView.head()'s AppBar; bottom action areas use SafeArea(top: false) to avoid system gesture areas.

/// Build page bottom action button.
///
/// [onSubmit] Submit callback.
Widget buildBottomAction(VoidCallback onSubmit) {
  return SafeArea(
    top: false,
    child: TDButton(text: 'Submit', onTap: onSubmit),
  );
}

For specific implementation code, please refer to: Screen Adaptation Documentation | Safe Area Documentation

6. Database Encapsulation

Local database capabilities based on sqflite, adopting a DataSource → Repository architecture. The business layer accesses the database through Repository.

Entity Definition (Simplified Example):

/// Demo table entity.
class DemoEntity {
  const DemoEntity({
    this.id,
    this.title = '',
    this.description,
  });

  final int? id;
  final String title;
  final String? description;

  /// Convert to database field Map.
  Map<String, dynamic> toMap({bool includeId = true}) {
    return <String, dynamic>{
      if (includeId) 'id': id,
      'title': title,
      'description': description,
    };
  }
}

Usage Example:

/// Database demo page Logic.
class DatabaseDemoLogic extends BaseLogic {
  /// Demo data repository.
  final DemoRepository _demoRepository = DemoRepository();

  /// Demo record list.
  final RxList<DemoEntity> items = <DemoEntity>[].obs;

  /// Save record.
  Future<void> save() async {
    await _demoRepository.createDemo('Sample Title', description: 'Sample Description');
    items.value = await _demoRepository.getAll();
  }

}

DatabaseProvider uniformly manages database connections, concurrent first-open, table creation, and closing. DataSource is responsible for SQL, table names, and field mapping. Features do not directly hold Database.

For specific implementation code, please refer to: Database Documentation

7. Local Storage

Lightweight local storage based on shared_preferences, adopting a DataSource → Repository architecture. The business layer accesses tokens, themes, languages, and user caches through Repository.

Usage Example:

/// Local storage demo page Logic.
class LocalStorageDemoLogic extends BaseLogic {
  /// User info storage repository.
  final UserInfoStoreRepository _userInfoStoreRepository =
      UserInfoStoreRepository();

  /// Current user info.
  final Rxn<User> user = Rxn<User>();

  /// Save user info.
  Future<void> saveUser() async {
    const User demoUser = User(
      id: 10086,
      nickName: 'Demo User',
      phone: '18800000000',
    );
    await _userInfoStoreRepository.saveUserInfo(demoUser);
    user.value = await _userInfoStoreRepository.getUserInfo();
  }

  /// Clear user info.
  Future<void> clearUser() async {
    await _userInfoStoreRepository.clearUserInfo();
    user.value = null;
  }
}

Storage keys, default values, and JSON conversion are all kept within the DataSource implementation. Corrupted user JSON is treated as no cache, preventing parse exceptions from being thrown directly to the page.

For specific implementation code, please refer to: Local Storage Documentation

Utility Classes

The framework includes common utilities to avoid repetitive implementation in the business layer:

For specific usage methods, please refer to: Utility Class Documentation

Project Structure

FlutterKit/
├── android / ios / web / ...  # Six-platform native projects
├── assets/                     # Images, icons, theme JSON, and generated source images
├── docs/                       # FlutterKit and TDesign local documentation
├── lib/
│   ├── bootstrap/              # Startup initializer
│   ├── core/                   # Core module
│   │   ├── base/               # Base parent classes
│   │   ├── data/               # Repository and Preview Data
│   │   ├── database/           # Database
│   │   ├── datastore/          # Local storage
│   │   ├── design_system/      # Design system and chain extensions
│   │   ├── model/              # Data models
│   │   ├── network/            # Network layer
│   │   ├── result/             # Result handling
│   │   ├── service/            # Global Services
│   │   └── ui / util / ...     # UI, responsive, and utility classes
│   ├── feature/                # Functional modules
│   │   ├── auth/               # Authentication module
│   │   ├── demo/               # Demo module
│   │   ├── main/               # Main module
│   │   └── user/               # User module
│   ├── routes/                 # Routes, parameters, results, and guards
│   ├── application.dart        # Initialization orchestration and app-level state
│   └── main.dart               # Flutter application entry point
├── test/                       # Unit tests and Widget tests
└── tool/                       # Scripts like coverage checks

Related Resources:

If this project helps you, please give a ⭐ Star to support it!

Comments

Top 3 of 4 from juejin.cn, machine-translated. The original thread is authoritative.

瑞克 1 likes

Bookmarking this.

JokerX  · 1 likes

[heart]

高成酱

Impressive, big shot. It'd be even better if you swapped out GetX.

用户288284567578

Not bad, pretty good.