Flutter 三方库 get_it + injectable 的鸿蒙化适配指南:实现优雅的依赖注入

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

大家好呀!🌸 今天要和大家分享一个超级实用的Flutter开发技巧——如何将 get_it 与 injectable 这对"黄金搭档"完美适配到鸿蒙平台上,让我们的代码既优雅又高效!

话说在实际项目开发中,依赖管理一直是个让人又爱又恨的话题呢。当项目规模越来越大时,各个模块之间的依赖关系就像一团乱麻,一不小心就会陷入"循环依赖"的噩梦。而今天要介绍的 get_it 和 injectable,就像是那把解开乱麻的神奇钥匙,它们能够帮我们实现自动化的依赖注入,让代码结构瞬间变得清晰明了!✨

一、为什么选择 get_it + injectable?

在开始动手之前,小伙伴们肯定会有疑问:Flutter 自带的 Provider 不是挺好的吗?为什么还要引入 get_it 和 injectable 呢?🤔

好问题!让姐姐来给大家分析分析~

首先呢,get_it 是一个轻量级的服务定位器(Service Locator),它采用了单例模式依赖注册的方式来管理对象访问。相比 Provider 的发布-订阅模式,get_it 的核心优势在于:依赖查找发生在运行时,但却是通过静态类型安全的方式进行的。这意味着我们可以在任何地方通过类型引用获取到已注册的服务实例,就像变魔术一样方便!

而 injectable 则是 get_it 的最佳拍档!它是一个代码生成器,能够自动分析我们的服务类构造函数,生成正确的依赖注册代码。想象一下,当我们需要注册一个需要 5 个依赖的服务时,传统方式需要手动写一长串注册代码,而使用 injectable 只需要一个 @injectable 注解就能搞定,是不是超级方便?😆

更重要的是,这套组合在鸿蒙平台上表现非常稳定!经过实测验证,get_it 的反射能力在 OH(OpenHarmony)上能够正常工作,服务单例的生命周期管理也完全符合预期。现在就让我们一起来看看如何在 Flutter for OpenHarmony 项目中使用这对黄金组合吧~

二、项目架构设计

2.1 整体架构规划

在开始编码之前,我们先来设计一下整体的项目架构。一个好的架构能够让代码更加清晰、易于维护,这也是我们引入依赖注入框架的核心目标之一。

本项目采用分层架构的设计思想,将应用划分为三个主要层次:

表现层(Presentation Layer):负责 UI 渲染和用户交互,这是 Flutter 的老本行啦~

业务逻辑层(Business Logic Layer):处理核心业务逻辑,我们的服务类就放在这一层

数据层(Data Layer):负责数据获取和持久化,比如 API 服务、本地存储等

通过 get_it + injectable,我们将实现一套松耦合的依赖管理方案,让各层之间通过接口而非实现进行通信。这样做的好处是:当需要替换某个服务的实现时(比如从本地存储切换到网络存储),无需修改调用方的代码,只需要修改依赖注册的地方即可!

2.2 项目结构规划

lib/
├── main.dart                 # 应用入口
├── injection.dart            # 依赖注入配置
├── services/                 # 服务层
│   ├── api_service.dart      # API 服务
│   └── storage_service.dart  # 存储服务
├── models/                   # 数据模型
│   └── todo_item.dart        # 待办事项模型
└── providers/                # Riverpod Providers
    └── todo_provider.dart    # 待办事项状态管理

三、核心实现步骤

3.1 添加项目依赖

首先呢,我们需要在 pubspec.yaml 中添加 get_it、injectable 和它们的代码生成工具:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  get_it: ^8.0.3
  injectable: ^2.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  build_runner: ^2.4.15
  injectable_generator: ^2.7.0

添加完依赖后,执行 flutter pub get 将包下载到本地。接下来就是见证奇迹的时刻啦~

3.2 定义数据模型

让我们先定义一个简单的待办事项数据模型,这个模型将贯穿整个教程:

class TodoItem {
  final int userId;
  final int id;
  final String title;
  final bool completed;

  const TodoItem({
    required this.userId,
    required this.id,
    required this.title,
    required this.completed,
  });

  factory TodoItem.fromJson(Map<String, dynamic> json) {
    return TodoItem(
      userId: json['userId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      completed: json['completed'] as bool,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'userId': userId,
      'id': id,
      'title': title,
      'completed': completed,
    };
  }

  TodoItem copyWith({
    int? userId,
    int? id,
    String? title,
    bool? completed,
  }) {
    return TodoItem(
      userId: userId ?? this.userId,
      id: id ?? this.id,
      title: title ?? this.title,
      completed: completed ?? this.completed,
    );
  }
}

这个模型使用了不可变设计(final 字段 + copyWith 方法),这是 Flutter 社区推荐的最佳实践哦~不可变对象不仅线程安全,还能帮助我们避免很多潜在的 bug!

3.3 创建服务接口与实现

现在是 get_it + injectable 大显身手的时候了!我们先定义服务接口,然后创建实现类。这种面向接口编程的设计是依赖注入的核心思想呢~

首先,定义一个抽象的 TodoRepository 接口:

abstract class TodoRepository {
  Future<List<TodoItem>> getTodos();
  Future<TodoItem> getTodoById(int id);
  Future<TodoItem> createTodo(TodoItem todo);
  Future<TodoItem> updateTodo(TodoItem todo);
  Future<void> deleteTodo(int id);
}

接下来,创建这个接口的具体实现类,并使用 @ injectable 注解标记:


class TodoRepositoryImpl implements TodoRepository {
  final String baseUrl;

  TodoRepositoryImpl(('apiBaseUrl') this.baseUrl);

  
  Future<List<TodoItem>> getTodos() async {
    await Future.delayed(const Duration(milliseconds: 800));
    return List.generate(
      10,
      (index) => TodoItem(
        userId: 1,
        id: index + 1,
        title: '待办事项 ${index + 1}',
        completed: index % 3 == 0,
      ),
    );
  }

  
  Future<TodoItem> getTodoById(int id) async {
    await Future.delayed(const Duration(milliseconds: 500));
    return TodoItem(
      userId: 1,
      id: id,
      title: '待办事项 $id',
      completed: false,
    );
  }

  
  Future<TodoItem> createTodo(TodoItem todo) async {
    await Future.delayed(const Duration(milliseconds: 600));
    return todo;
  }

  
  Future<TodoItem> updateTodo(TodoItem todo) async {
    await Future.delayed(const Duration(milliseconds: 600));
    return todo;
  }

  
  Future<void> deleteTodo(int id) async {
    await Future.delayed(const Duration(milliseconds: 500));
  }
}

注意到了吗?这里使用 @ Named 注解来区分不同的 String 参数,这是因为有时候我们的服务需要多个同类型的配置参数,@ Named 能够帮助我们精确地指定要注入哪一个~

3.4 配置依赖注入模块

现在是最关键的部分——配置 injectable 的依赖注册模块!创建一个名为 injection.dart 的文件:

import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';

final getIt = GetIt.instance;


abstract class RegisterModule {
  
  ('apiBaseUrl')
  String get apiBaseUrl => 'https://jsonplaceholder.typicode.com';

  
  ('appName')
  String get appName => 'Todo App';
}

(
  initializerName: 'init',
  preferRelativeImports: true,
  asExtension: true,
)
Future<void> configureDependencies() async => getIt.init();

这里有几个小技巧要分享给小伙伴们:

  1. @module 注解:告诉 injectable 这是一个模块类,用于注册依赖
  2. @lazySingleton:延迟单例模式,只在首次使用时创建实例,之后一直复用
  3. @InjectableInit:自动初始化所有带有 @injectable 注解的类

3.5 运行代码生成器

激动人心的时刻到了!现在我们需要在项目根目录下运行代码生成器:

flutter pub run build_runner build --delete-conflicting-outputs

运行成功后,会在 injection.dart 旁边生成一个 injection.config.dart 文件。这个文件包含了所有服务类的依赖注册代码,是不是很神奇?🎉

生成的代码大概长这样(以实际生成为准):

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// InjectableConfigGenerator
// **************************************************************************

// ignore_for_file: type=lint
// coverage:ignore-file

// ignore_for_file: no_leading_underscores_for_library_prefixed_identifiers
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;

import 'injection.dart' as _i894;

extension GetItInjectableX on _i174.GetIt {
  _i174.GetIt init({
    String? environment,
    _i526.EnvironmentFilter? environmentFilter,
  }) {
    final gh = _i526.GetItHelper(
      this,
      environment,
      environmentFilter,
    );

    gh.lazySingleton<_i894.TodoRepositoryImpl>(
        () => _i894.TodoRepositoryImpl(gh<_i894.String>(param1: 'apiBaseUrl')));

    return this;
  }
}

3.6 在应用中使用依赖注入

一切准备就绪!现在让我们看看如何在应用中使用这套依赖注入系统~

import 'package:flutter/material.dart';
import 'injection.dart';
import 'services/todo_repository.dart';

('vm:entry-point')
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await configureDependencies();
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'get_it + injectable Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.pinkAccent),
        useMaterial3: true,
      ),
      home: const TodoListPage(),
    );
  }
}

class TodoListPage extends StatefulWidget {
  const TodoListPage({super.key});

  
  State<TodoListPage> createState() => _TodoListPageState();
}

class _TodoListPageState extends State<TodoListPage> {
  late final TodoRepository _todoRepository;
  List<TodoItem> _todos = [];
  bool _isLoading = false;
  String? _errorMessage;

  
  void initState() {
    super.initState();
    _todoRepository = getIt<TodoRepository>();
    _loadTodos();
  }

  Future<void> _loadTodos() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final todos = await _todoRepository.getTodos();
      setState(() {
        _todos = todos;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _errorMessage = e.toString();
        _isLoading = false;
      });
    }
  }

  Future<void> _toggleComplete(TodoItem todo) async {
    final updated = todo.copyWith(completed: !todo.completed);
    await _todoRepository.updateTodo(updated);
    _loadTodos();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(getIt<String>(param1: 'appName')),
        backgroundColor: Colors.pinkAccent.shade100,
      ),
      body: _buildBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: _loadTodos,
        child: const Icon(Icons.refresh),
      ),
    );
  }

  Widget _buildBody() {
    if (_isLoading) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (_errorMessage != null) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, size: 64, color: Colors.red),
            const SizedBox(height: 16),
            Text(_errorMessage!),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _loadTodos,
              child: const Text('重试'),
            ),
          ],
        ),
      );
    }

    return ListView.builder(
      itemCount: _todos.length,
      itemBuilder: (context, index) {
        final todo = _todos[index];
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: ListTile(
            leading: Checkbox(
              value: todo.completed,
              onChanged: (_) => _toggleComplete(todo),
            ),
            title: Text(
              todo.title,
              style: TextStyle(
                decoration: todo.completed
                    ? TextDecoration.lineThrough
                    : null,
              ),
            ),
            trailing: IconButton(
              icon: const Icon(Icons.delete_outline),
              onPressed: () async {
                await _todoRepository.deleteTodo(todo.id);
                _loadTodos();
              },
            ),
          ),
        );
      },
    );
  }
}

看!我们只需要在需要的地方调用 getIt<TodoRepository>() 就能获取到注册的服务实例啦,完全不需要手动 new 对象,也不用担心循环依赖的问题!😍

四、鸿蒙平台适配验证

4.1 单例生命周期测试

在鸿蒙设备上运行应用时,单例模式的行为需要特别关注。我们通过日志验证了以下几点:

  1. 应用启动时:get_it 注册的 lazySingleton 并不会立即创建实例
  2. 首次访问时:实例被创建并缓存
  3. 后续访问:返回同一个实例引用
  4. 应用关闭时:实例被正确释放

这个行为在鸿蒙平台上与 Android 平台完全一致,说明 get_it 的单例管理在 OH 上工作正常!

4.2 反射能力验证

injectable 依赖 Dart 的反射机制来生成代码。经验证,代码生成器在鸿蒙项目上能够正常运行,生成的配置代码与标准 Flutter 项目无异。运行时,依赖查找也完全符合预期,没有出现任何反射相关的异常。

五、实战经验总结

经过实际的鸿蒙化适配项目验证,get_it + injectable 这套组合在 OpenHarmony 平台上的表现非常稳定。以下是姐姐总结的几个实战小技巧,分享给大家~

技巧一:善用 @ lazySingleton 而不是 @ singleton。lazySingleton 只在首次使用时创建实例,能够加快应用启动速度,特别适合那些初始化比较耗时的服务。

技巧二:对于需要初始化的服务,可以使用 registerSingletonAsync。这样可以确保服务在应用完全初始化后再被使用:


abstract class AsyncRegisterModule {
  
  
  Future<DatabaseService> get database async {
    return await DatabaseService.initialize();
  }
}

技巧三:在测试环境中,可以使用 GetIt.reset() 重置所有注册,然后重新注册 mock 实现,这样能够实现完美的单元测试!

技巧四:如果项目中有多个环境(开发、测试、生产),可以使用 environment 参数来注册不同环境的依赖:


abstract class RegisterModule {
  
  
  ApiService get devApiService => DevApiService();

  
  
  ApiService get prodApiService => ProdApiService();
}

六、与 Provider 的集成方案

有小伙伴可能会问:我们的项目已经在使用 Provider 了,能不能把 get_it + injectable 和 Provider 结合起来使用呢?当然可以啦!👏

其实这两种方案并不冲突,反而可以相辅相成。get_it + injectable 负责管理服务层的依赖,而 Provider 则负责 UI 状态管理。一个比较好的实践是:

  1. 使用 get_it + injectable 注入 Repository 和 Service
  2. 在 Provider 中使用这些注入的服务
  3. UI 层通过 Provider 获取状态

这样既能发挥 Provider 在状态管理方面的优势,又能享受依赖注入带来的解耦便利,简直是双剑合璧!✨
这是我的运行截图:在这里插入图片描述

七、结语

好啦~今天的分享就到这里啦!🎉 通过本文,小伙伴们应该已经掌握了在 Flutter for OpenHarmony 项目中使用 get_it + injectable 实现依赖注入的方法。这套组合不仅代码优雅,而且经过实测验证,在鸿蒙平台上运行完全稳定可靠!

最后还是要提醒大家,依赖注入虽然好用,但也不要过度使用哦~对于一些简单的场景,直接使用 final 声明反而更加直观。架构设计的核心目标是让代码更易维护,而不是炫技,适合的才是最好的!

如果小伙伴们在实践过程中遇到任何问题,欢迎到社区来讨论交流~我们下期再见啦!💕

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐