3.3 第三方框架

Flutter 生态中有多种成熟的第三方状态管理框架,各有侧重。本节介绍 GetX、Riverpod 和 Bloc/Cubit 三种主流方案。


一、GetX

GetX 是功能最全面的一体化框架,集状态管理、路由、依赖注入于一身,以极简代码著称。

安装

yaml 复制代码
dependencies:
  get: ^4.6.6

1.1 依赖注入:Get.put / Get.find

dart 复制代码
// Get.put:立即注册并创建实例
void main() {
  Get.put(ApiService());
  Get.put(UserController());
  runApp(const MyApp());
}

// Get.lazyPut:懒加载,第一次使用时才创建
Get.lazyPut<ProductController>(() => ProductController());

// Get.find:在任意地方获取已注册的实例
final userController = Get.find<UserController>();

// 永久保留(不随页面关闭销毁)
Get.put(AppConfigController(), permanent: true);

// 标签区分多个同类实例
Get.put(CartController(), tag: 'main_cart');
final cart = Get.find<CartController>(tag: 'main_cart');

1.2 GetxController:控制器封装

dart 复制代码
class UserController extends GetxController {
  // 响应式变量(.obs)
  final RxString name = ''.obs;
  final RxBool isLoggedIn = false.obs;
  final Rx<User?> currentUser = Rx<User?>(null);
  final RxList<Order> orders = <Order>[].obs;

  @override
  void onInit() {
    super.onInit();
    // 初始化时执行
    loadUserData();
  }

  @override
  void onClose() {
    // 控制器销毁时清理
    super.onClose();
  }

  Future<void> loadUserData() async {
    try {
      final user = await ApiService.fetchUser();
      currentUser.value = user;
      name.value = user.name;
      isLoggedIn.value = true;
    } catch (e) {
      print('Load user error: $e');
    }
  }

  Future<void> logout() async {
    await ApiService.logout();
    currentUser.value = null;
    isLoggedIn.value = false;
    orders.clear();
    Get.offAllNamed('/login'); // 清空路由栈跳转登录
  }
}

1.3 Obx 响应式监听

dart 复制代码
class UserProfilePage extends StatelessWidget {
  final controller = Get.find<UserController>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() {
        // 只有响应式变量(.obs)变化时,Obx 内部才重建
        if (!controller.isLoggedIn.value) {
          return const LoginPrompt();
        }
        return Column(
          children: [
            Text('Welcome, ${controller.name.value}'),
            // 列表响应式:增删改都会触发重建
            Obx(() => ListView.builder(
              itemCount: controller.orders.length,
              itemBuilder: (_, i) => OrderTile(order: controller.orders[i]),
            )),
          ],
        );
      }),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.logout,
        child: const Icon(Icons.logout),
      ),
    );
  }
}

1.4 GetBuilder(非响应式,手动更新)

dart 复制代码
class CounterController extends GetxController {
  int count = 0; // 普通变量(非 .obs)

  void increment() {
    count++;
    update(); // 手动触发 GetBuilder 重建
  }
}

GetBuilder<CounterController>(
  builder: (controller) => Text('${controller.count}'),
)

二、Riverpod(推荐)

Riverpod 是 Provider 的进化版,编译期安全、可测试性极强,是当前 Flutter 社区最推荐的状态管理方案。

安装

yaml 复制代码
dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.4

dev_dependencies:
  riverpod_generator: ^2.3.10
  build_runner: ^2.4.0

2.1 ProviderScope

dart 复制代码
void main() {
  runApp(
    const ProviderScope( // ⚠️ 必须包裹根 Widget
      child: MyApp(),
    ),
  );
}

2.2 使用 @riverpod 注解(推荐代码生成)

dart 复制代码
// user_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_provider.g.dart'; // 代码生成文件

// 同步 Provider
@riverpod
String greeting(GreetingRef ref) => 'Hello, Flutter!';

// 异步 Provider
@riverpod
Future<User> currentUser(CurrentUserRef ref) async {
  return await ApiService.fetchCurrentUser();
}

// 带参数的 Provider(family)
@riverpod
Future<Product> productDetail(ProductDetailRef ref, int productId) async {
  return await ApiService.fetchProduct(productId);
}

// 运行代码生成
// dart run build_runner build

2.3 StateNotifier / AsyncNotifier

dart 复制代码
// 定义状态类
@immutable
class CartState {
  final List<CartItem> items;
  final bool isLoading;

  const CartState({this.items = const [], this.isLoading = false});

  CartState copyWith({List<CartItem>? items, bool? isLoading}) =>
      CartState(items: items ?? this.items, isLoading: isLoading ?? this.isLoading);

  double get totalPrice =>
      items.fold(0, (sum, item) => sum + item.price * item.quantity);
}

// AsyncNotifier:适合有异步初始化的状态
@riverpod
class CartNotifier extends _$CartNotifier {
  @override
  Future<CartState> build() async {
    // build() 是初始化逻辑
    final savedItems = await CartRepository.loadSavedCart();
    return CartState(items: savedItems);
  }

  Future<void> addItem(Product product) async {
    final current = await future; // 等待当前状态
    state = AsyncData(current.copyWith(
      items: [...current.items, CartItem.fromProduct(product)],
    ));
    await CartRepository.saveCart(state.value!.items);
  }

  Future<void> removeItem(String id) async {
    state = state.when(
      data: (cart) => AsyncData(
        cart.copyWith(items: cart.items.where((i) => i.id != id).toList()),
      ),
      loading: () => const AsyncLoading(),
      error: (e, s) => AsyncError(e, s),
    );
  }
}

2.4 在 Widget 中消费 Provider

dart 复制代码
// 使用 ConsumerWidget 替代 StatelessWidget
class CartPage extends ConsumerWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听异步状态
    final cartAsync = ref.watch(cartNotifierProvider);

    return cartAsync.when(
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
      data: (cart) => Column(
        children: [
          CartItemList(items: cart.items),
          Text('总计:¥${cart.totalPrice.toStringAsFixed(2)}'),
          ElevatedButton(
            onPressed: () => ref.read(cartNotifierProvider.notifier).clear(),
            child: const Text('清空购物车'),
          ),
        ],
      ),
    );
  }
}

// 使用 ConsumerStatefulWidget 替代 StatefulWidget
class SearchPage extends ConsumerStatefulWidget {
  @override
  ConsumerState<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends ConsumerState<SearchPage> {
  late TextEditingController _searchController;

  @override
  void initState() {
    super.initState();
    _searchController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    // 使用 ref.watch 监听
    final query = ref.watch(searchQueryProvider);
    final results = ref.watch(searchResultsProvider(query));

    return Column(
      children: [
        TextField(
          controller: _searchController,
          onChanged: (q) => ref.read(searchQueryProvider.notifier).state = q,
        ),
        results.when(
          data: (list) => SearchResults(results: list),
          loading: () => const CircularProgressIndicator(),
          error: (e, _) => const SizedBox.shrink(),
        ),
      ],
    );
  }
}

三、Bloc / Cubit

Bloc 是事件驱动的状态管理框架,强调明确的数据流,适合大型复杂业务。

安装

yaml 复制代码
dependencies:
  flutter_bloc: ^8.1.5

3.1 Cubit(简化版 Bloc)

dart 复制代码
// Cubit 直接暴露方法,无需事件类
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0); // 初始状态

  void increment() => emit(state + 1);
  void decrement() => emit(state > 0 ? state - 1 : 0);
  void reset() => emit(0);
}

// 使用
BlocProvider(
  create: (_) => CounterCubit(),
  child: BlocBuilder<CounterCubit, int>(
    builder: (context, count) {
      return Text('$count');
    },
  ),
)

3.2 Bloc(完整事件驱动)

dart 复制代码
// 1. 定义事件
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email;
  final String password;
  LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}
class TokenRefreshRequested extends AuthEvent {}

// 2. 定义状态
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  AuthAuthenticated(this.user);
}
class AuthUnauthenticated extends AuthState {}
class AuthError extends AuthState {
  final String message;
  AuthError(this.message);
}

// 3. 实现 Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _authRepository;

  AuthBloc(this._authRepository) : super(AuthInitial()) {
    on<LoginRequested>(_onLoginRequested);
    on<LogoutRequested>(_onLogoutRequested);
    on<TokenRefreshRequested>(_onTokenRefresh);
  }

  Future<void> _onLoginRequested(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    try {
      final user = await _authRepository.login(
        email: event.email,
        password: event.password,
      );
      emit(AuthAuthenticated(user));
    } on AuthException catch (e) {
      emit(AuthError(e.message));
    } catch (e) {
      emit(AuthError('Network error'));
    }
  }

  Future<void> _onLogoutRequested(
    LogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    await _authRepository.logout();
    emit(AuthUnauthenticated());
  }

  Future<void> _onTokenRefresh(
    TokenRefreshRequested event,
    Emitter<AuthState> emit,
  ) async {
    // 静默刷新 token
    try {
      await _authRepository.refreshToken();
    } catch (_) {
      emit(AuthUnauthenticated());
    }
  }
}

3.3 BlocProvider / BlocBuilder / BlocListener

dart 复制代码
// 提供 Bloc
BlocProvider(
  create: (context) => AuthBloc(context.read<AuthRepository>()),
  child: const LoginPage(),
)

// BlocBuilder:根据状态构建 UI
BlocBuilder<AuthBloc, AuthState>(
  // buildWhen:减少不必要的重建
  buildWhen: (previous, current) => previous.runtimeType != current.runtimeType,
  builder: (context, state) {
    return switch (state) {
      AuthLoading() => const CircularProgressIndicator(),
      AuthAuthenticated(:final user) => UserDashboard(user: user),
      AuthUnauthenticated() => const LoginPage(),
      AuthError(:final message) => ErrorWidget(message: message),
      _ => const SizedBox.shrink(),
    };
  },
)

// BlocListener:监听状态变化执行副作用(导航、弹窗等)
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthAuthenticated) {
      Navigator.pushReplacementNamed(context, '/home');
    } else if (state is AuthError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: const LoginForm(),
)

// BlocConsumer = BlocBuilder + BlocListener
BlocConsumer<AuthBloc, AuthState>(
  listener: (context, state) {
    // 副作用
  },
  builder: (context, state) {
    return const LoginForm(); // UI
  },
)

// 触发事件
context.read<AuthBloc>().add(LoginRequested(
  email: _email,
  password: _password,
));

四、方案选型建议

场景 推荐方案
快速原型 / 小项目 GetX
中大型项目 / 现代化架构 Riverpod
企业级 / 严格分层 / 多人协作 Bloc
Flutter 官方风格 Provider

👉 下一节:3.4 状态同步与生命周期管理

3.3 第三方框架

Flutter 生态中有多种成熟的第三方状态管理框架,各有侧重。本节介绍 GetX、Riverpod 和 Bloc/Cubit 三种主流方案。


一、GetX

GetX 是功能最全面的一体化框架,集状态管理、路由、依赖注入于一身,以极简代码著称。

安装

yaml 复制代码
dependencies:
  get: ^4.6.6

1.1 依赖注入:Get.put / Get.find

dart 复制代码
// Get.put:立即注册并创建实例
void main() {
  Get.put(ApiService());
  Get.put(UserController());
  runApp(const MyApp());
}

// Get.lazyPut:懒加载,第一次使用时才创建
Get.lazyPut<ProductController>(() => ProductController());

// Get.find:在任意地方获取已注册的实例
final userController = Get.find<UserController>();

// 永久保留(不随页面关闭销毁)
Get.put(AppConfigController(), permanent: true);

// 标签区分多个同类实例
Get.put(CartController(), tag: 'main_cart');
final cart = Get.find<CartController>(tag: 'main_cart');

1.2 GetxController:控制器封装

dart 复制代码
class UserController extends GetxController {
  // 响应式变量(.obs)
  final RxString name = ''.obs;
  final RxBool isLoggedIn = false.obs;
  final Rx<User?> currentUser = Rx<User?>(null);
  final RxList<Order> orders = <Order>[].obs;

  @override
  void onInit() {
    super.onInit();
    // 初始化时执行
    loadUserData();
  }

  @override
  void onClose() {
    // 控制器销毁时清理
    super.onClose();
  }

  Future<void> loadUserData() async {
    try {
      final user = await ApiService.fetchUser();
      currentUser.value = user;
      name.value = user.name;
      isLoggedIn.value = true;
    } catch (e) {
      print('Load user error: $e');
    }
  }

  Future<void> logout() async {
    await ApiService.logout();
    currentUser.value = null;
    isLoggedIn.value = false;
    orders.clear();
    Get.offAllNamed('/login'); // 清空路由栈跳转登录
  }
}

1.3 Obx 响应式监听

dart 复制代码
class UserProfilePage extends StatelessWidget {
  final controller = Get.find<UserController>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() {
        // 只有响应式变量(.obs)变化时,Obx 内部才重建
        if (!controller.isLoggedIn.value) {
          return const LoginPrompt();
        }
        return Column(
          children: [
            Text('Welcome, ${controller.name.value}'),
            // 列表响应式:增删改都会触发重建
            Obx(() => ListView.builder(
              itemCount: controller.orders.length,
              itemBuilder: (_, i) => OrderTile(order: controller.orders[i]),
            )),
          ],
        );
      }),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.logout,
        child: const Icon(Icons.logout),
      ),
    );
  }
}

1.4 GetBuilder(非响应式,手动更新)

dart 复制代码
class CounterController extends GetxController {
  int count = 0; // 普通变量(非 .obs)

  void increment() {
    count++;
    update(); // 手动触发 GetBuilder 重建
  }
}

GetBuilder<CounterController>(
  builder: (controller) => Text('${controller.count}'),
)

二、Riverpod(推荐)

Riverpod 是 Provider 的进化版,编译期安全、可测试性极强,是当前 Flutter 社区最推荐的状态管理方案。

安装

yaml 复制代码
dependencies:
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.4

dev_dependencies:
  riverpod_generator: ^2.3.10
  build_runner: ^2.4.0

2.1 ProviderScope

dart 复制代码
void main() {
  runApp(
    const ProviderScope( // ⚠️ 必须包裹根 Widget
      child: MyApp(),
    ),
  );
}

2.2 使用 @riverpod 注解(推荐代码生成)

dart 复制代码
// user_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user_provider.g.dart'; // 代码生成文件

// 同步 Provider
@riverpod
String greeting(GreetingRef ref) => 'Hello, Flutter!';

// 异步 Provider
@riverpod
Future<User> currentUser(CurrentUserRef ref) async {
  return await ApiService.fetchCurrentUser();
}

// 带参数的 Provider(family)
@riverpod
Future<Product> productDetail(ProductDetailRef ref, int productId) async {
  return await ApiService.fetchProduct(productId);
}

// 运行代码生成
// dart run build_runner build

2.3 StateNotifier / AsyncNotifier

dart 复制代码
// 定义状态类
@immutable
class CartState {
  final List<CartItem> items;
  final bool isLoading;

  const CartState({this.items = const [], this.isLoading = false});

  CartState copyWith({List<CartItem>? items, bool? isLoading}) =>
      CartState(items: items ?? this.items, isLoading: isLoading ?? this.isLoading);

  double get totalPrice =>
      items.fold(0, (sum, item) => sum + item.price * item.quantity);
}

// AsyncNotifier:适合有异步初始化的状态
@riverpod
class CartNotifier extends _$CartNotifier {
  @override
  Future<CartState> build() async {
    // build() 是初始化逻辑
    final savedItems = await CartRepository.loadSavedCart();
    return CartState(items: savedItems);
  }

  Future<void> addItem(Product product) async {
    final current = await future; // 等待当前状态
    state = AsyncData(current.copyWith(
      items: [...current.items, CartItem.fromProduct(product)],
    ));
    await CartRepository.saveCart(state.value!.items);
  }

  Future<void> removeItem(String id) async {
    state = state.when(
      data: (cart) => AsyncData(
        cart.copyWith(items: cart.items.where((i) => i.id != id).toList()),
      ),
      loading: () => const AsyncLoading(),
      error: (e, s) => AsyncError(e, s),
    );
  }
}

2.4 在 Widget 中消费 Provider

dart 复制代码
// 使用 ConsumerWidget 替代 StatelessWidget
class CartPage extends ConsumerWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听异步状态
    final cartAsync = ref.watch(cartNotifierProvider);

    return cartAsync.when(
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
      data: (cart) => Column(
        children: [
          CartItemList(items: cart.items),
          Text('总计:¥${cart.totalPrice.toStringAsFixed(2)}'),
          ElevatedButton(
            onPressed: () => ref.read(cartNotifierProvider.notifier).clear(),
            child: const Text('清空购物车'),
          ),
        ],
      ),
    );
  }
}

// 使用 ConsumerStatefulWidget 替代 StatefulWidget
class SearchPage extends ConsumerStatefulWidget {
  @override
  ConsumerState<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends ConsumerState<SearchPage> {
  late TextEditingController _searchController;

  @override
  void initState() {
    super.initState();
    _searchController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {
    // 使用 ref.watch 监听
    final query = ref.watch(searchQueryProvider);
    final results = ref.watch(searchResultsProvider(query));

    return Column(
      children: [
        TextField(
          controller: _searchController,
          onChanged: (q) => ref.read(searchQueryProvider.notifier).state = q,
        ),
        results.when(
          data: (list) => SearchResults(results: list),
          loading: () => const CircularProgressIndicator(),
          error: (e, _) => const SizedBox.shrink(),
        ),
      ],
    );
  }
}

三、Bloc / Cubit

Bloc 是事件驱动的状态管理框架,强调明确的数据流,适合大型复杂业务。

安装

yaml 复制代码
dependencies:
  flutter_bloc: ^8.1.5

3.1 Cubit(简化版 Bloc)

dart 复制代码
// Cubit 直接暴露方法,无需事件类
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0); // 初始状态

  void increment() => emit(state + 1);
  void decrement() => emit(state > 0 ? state - 1 : 0);
  void reset() => emit(0);
}

// 使用
BlocProvider(
  create: (_) => CounterCubit(),
  child: BlocBuilder<CounterCubit, int>(
    builder: (context, count) {
      return Text('$count');
    },
  ),
)

3.2 Bloc(完整事件驱动)

dart 复制代码
// 1. 定义事件
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email;
  final String password;
  LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}
class TokenRefreshRequested extends AuthEvent {}

// 2. 定义状态
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  AuthAuthenticated(this.user);
}
class AuthUnauthenticated extends AuthState {}
class AuthError extends AuthState {
  final String message;
  AuthError(this.message);
}

// 3. 实现 Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _authRepository;

  AuthBloc(this._authRepository) : super(AuthInitial()) {
    on<LoginRequested>(_onLoginRequested);
    on<LogoutRequested>(_onLogoutRequested);
    on<TokenRefreshRequested>(_onTokenRefresh);
  }

  Future<void> _onLoginRequested(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    try {
      final user = await _authRepository.login(
        email: event.email,
        password: event.password,
      );
      emit(AuthAuthenticated(user));
    } on AuthException catch (e) {
      emit(AuthError(e.message));
    } catch (e) {
      emit(AuthError('Network error'));
    }
  }

  Future<void> _onLogoutRequested(
    LogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    await _authRepository.logout();
    emit(AuthUnauthenticated());
  }

  Future<void> _onTokenRefresh(
    TokenRefreshRequested event,
    Emitter<AuthState> emit,
  ) async {
    // 静默刷新 token
    try {
      await _authRepository.refreshToken();
    } catch (_) {
      emit(AuthUnauthenticated());
    }
  }
}

3.3 BlocProvider / BlocBuilder / BlocListener

dart 复制代码
// 提供 Bloc
BlocProvider(
  create: (context) => AuthBloc(context.read<AuthRepository>()),
  child: const LoginPage(),
)

// BlocBuilder:根据状态构建 UI
BlocBuilder<AuthBloc, AuthState>(
  // buildWhen:减少不必要的重建
  buildWhen: (previous, current) => previous.runtimeType != current.runtimeType,
  builder: (context, state) {
    return switch (state) {
      AuthLoading() => const CircularProgressIndicator(),
      AuthAuthenticated(:final user) => UserDashboard(user: user),
      AuthUnauthenticated() => const LoginPage(),
      AuthError(:final message) => ErrorWidget(message: message),
      _ => const SizedBox.shrink(),
    };
  },
)

// BlocListener:监听状态变化执行副作用(导航、弹窗等)
BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthAuthenticated) {
      Navigator.pushReplacementNamed(context, '/home');
    } else if (state is AuthError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: const LoginForm(),
)

// BlocConsumer = BlocBuilder + BlocListener
BlocConsumer<AuthBloc, AuthState>(
  listener: (context, state) {
    // 副作用
  },
  builder: (context, state) {
    return const LoginForm(); // UI
  },
)

// 触发事件
context.read<AuthBloc>().add(LoginRequested(
  email: _email,
  password: _password,
));

四、方案选型建议

场景 推荐方案
快速原型 / 小项目 GetX
中大型项目 / 现代化架构 Riverpod
企业级 / 严格分层 / 多人协作 Bloc
Flutter 官方风格 Provider

👉 下一节:3.4 状态同步与生命周期管理

相关推荐
麒麟ZHAO2 小时前
鸿蒙flutter第三方库适配 - 文件搜索工具
flutter·华为·harmonyos
2401_839633912 小时前
鸿蒙flutter第三方库适配 - 二维表格
flutter·华为·harmonyos
麒麟ZHAO2 小时前
鸿蒙flutter第三方库适配 - Google登录演示
flutter·华为·harmonyos
2401_839633912 小时前
鸿蒙flutter第三方库适配 - 日历网格
flutter·华为·harmonyos
2401_839633912 小时前
鸿蒙flutter第三方库适配 - 数据网格
flutter·华为·harmonyos
见山是山-见水是水3 小时前
鸿蒙flutter第三方库适配 - 汇率换算器
redis·flutter·华为·harmonyos
麒麟ZHAO3 小时前
Flutter 框架跨平台鸿蒙开发 - 智能衣柜衣物换季
flutter·华为·harmonyos
麒麟ZHAO3 小时前
鸿蒙flutter第三方库适配 - 实时天气查询
flutter·华为·harmonyos
autumn200513 小时前
Flutter 框架跨平台鸿蒙开发 - 虚拟纪念馆
flutter·华为·harmonyos