簡體   English   中英

在 tabview 中使用 flutter_bloc

[英]Using flutter_bloc with tabview

我有一個 tabview,我有流行的、最近的和即將到來的類別。 他們在 api 上都有相同的響應。 我正在嘗試使用 flutter_bloc 從 api 獲取數據。 以前我使用 rxdart 主題,我為每種類型的數據制作了一個主題。 現在使用顫振塊我想達到同樣的目的。 我想要做的是在選項卡之間切換。 以前我使用 behaviorsubject 將數據保存到下一個事件,但現在我想轉換為 bloc 模式。 如何使用 flutter_bloc 獲得相同類型的結果? 或者我需要為每種類型創建塊? 最后,如何從 api 獲取數據,以便在切換選項卡時保持狀態? 我的 Rxdart 實現:

class DataBloc {
  final DataRepo _repository = DataRepo();
  final BehaviorSubject<Data> _recent = BehaviorSubject<Data>();
  final BehaviorSubject<Data> _popular = BehaviorSubject<Data>();
  final BehaviorSubject<Data> _upcoming = BehaviorSubject<Data>();
  
getData(String type) async {
    
    Data response = await _repository.getData(type);
    if (type == "recent") {
      _recent.sink.add(response);
    } else if (type == "upcoming") {
      _upcoming.sink.add(response);
    } else {
      _popular.sink.add(response);
    }
  }

  dispose() {
    _recent?.close();
    _popular?.close();
    _upcoming?.close();
  }

  BehaviorSubject<Data> get recent => _recent;
  BehaviorSubject<Data> get popular => _popular;
  BehaviorSubject<Data> get upcoming => _upcoming;
}

對於您的問題,肯定沒有單一的解決方案。 我會回答你的問題,我會給你一個完整的實現/示例,以便於理解。

我需要為每種類型創建塊嗎?

我建議您為每個數據創建一個 BLoC(如示例中所示),因為它會簡化 BLoC 的邏輯(特別是如果您不想一次加載所有數據)並且應用程序將結構化並且更少耦合哪個好。 但是,如果您願意,您仍然可以在一個 BLoC 中完成此操作。

如何從 api 獲取數據,以便在切換選項卡時保持狀態?

是的只要它使用相同的 BLoC/Cubit 實例,狀態就會持久化。 每次構建 bloc 時(使用BlocBuilder ),您都會獲得最后一個狀態。 在我的示例中,我們僅在呈現選項卡視圖時調用一次load()事件。

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  ///
  /// The repository that is shared among all BLOCs
  ///
  final Repository repository = Repository();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    // For example purpose I will expose them as global cubits
    return MultiBlocProvider(
        providers: [
          BlocProvider<PopularCategoriesCubit>(
            create: (context) =>
                // Create the cubit and also call the LOAD event right away.
                //
                // NOTE #4. The cubit is created only when is requested (by
                // BlocBuilder, BlocListener, etc). This is why when you move to
                // a FRESH NEW tab you see the loading state.
                //
                PopularCategoriesCubit(repository: repository)..load(),
          ),
          BlocProvider<RecentCategoriesCubit>(
            create: (context) =>
                RecentCategoriesCubit(repository: repository)..load(),
          ),
          BlocProvider<UpcomingCategoriesCubit>(
            create: (context) =>
                UpcomingCategoriesCubit(repository: repository)..load(),
          ),
        ],
        child: MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: MyHomePage(),
        ));
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Bloc Tabs"),
          bottom: TabBar(
            tabs: [
              Tab(text: "Popular"),
              Tab(text: "Recent"),
              Tab(text: "Upcoming"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            // POPULAR TAB
            BlocBuilder<PopularCategoriesCubit, GenericState>(
              builder: (context, state) {
                if (state.isFailed)
                  return Text("Failed to fetch popular categories.");

                if (state.isLoading)
                  return Text("Loading popular categories...");

                return ListView(
                  children: [
                    for (var category in state.categories) Text(category)
                  ],
                );
              },
            ),

            // RECENT TAB
            BlocBuilder<RecentCategoriesCubit, GenericState>(
              builder: (context, state) {
                if (state.isFailed)
                  return Text("Failed to fetch recent categories.");

                if (state.isLoading)
                  return Text("Loading recent categories...");

                return ListView(
                  children: [
                    for (var category in state.categories) Text(category)
                  ],
                );
              },
            ),

            // UPCOMMING TAB
            BlocBuilder<UpcomingCategoriesCubit, GenericState>(
              builder: (context, state) {
                if (state.isFailed)
                  return Text("Failed to fetch upcoming categories.");

                if (state.isLoading)
                  return Text("Loading upcoming categories...");

                return ListView(
                  children: [
                    for (var category in state.categories) Text(category)
                  ],
                );
              },
            ),
          ],
        ),
        // This trailing comma makes auto-formatting nicer for build methods.
      ),
    );
  }
}

// =============================================================================

///
/// Repository Mock
///
class Repository {
  ///
  /// Retreive data by type.
  ///
  /// NOTE #1. Is better to use enum instead of String.
  ///
  Future<List<String>> getData(String type) async {
    // Emulate netword delay
    return Future<List<String>>.delayed(Duration(seconds: 2)).then((_) {
      switch (type) {
        case "popular":
          return [
            "Popular 1",
            "Popular 2",
            "Popular 3",
            "Popular 5",
            "Popular 6"
          ];

        case "recent":
          return [
            "Recent 1",
            "Recent 2",
            "Recent 3",
          ];

        case "upcoming":
        default:
          return [
            "Upcomming 1",
            "Upcomming 2",
          ];
      }
    });
  }
}

///
/// This is a generic state used for all categories types
///
/// NOTE #2. Use Equatable. Also if you feel you can break this GenericState in
/// multiple classes as CategoriesLoadedState, CategoriesLoadingState,
/// CategoriesFailedState ...
///
class GenericState {
  ///
  /// Categories data
  ///
  final List<String> categories;

  ///
  /// Tells the data is loading or not
  ///
  final bool isLoading;

  ///
  /// Tells whether the state has errors or not
  ///
  final bool isFailed;

  GenericState(
      {this.categories, this.isLoading = false, this.isFailed = false});
}

///
/// Popular categories Cubit
///
class PopularCategoriesCubit extends Cubit<GenericState> {
  ///
  /// Repository dependency
  ///
  final Repository repository;

  ///
  /// Cubit constructor. Send a loading state as default.
  ///
  PopularCategoriesCubit({@required this.repository})
      : super(GenericState(isLoading: true));

  // ==================================
  // EVENTS
  // ==================================

  ///
  /// Load data from repository
  ///
  void load() async {
    //#log
    print("[EVENT] Popular Categories :: Load");

    // Every time when try to load data from repository put the application
    // in a loading state
    emit(GenericState(isLoading: true));

    try {
      // Wait for data from repository
      List categories = await this.repository.getData("popular");

      // Send a success state
      emit(GenericState(categories: categories, isFailed: false));
    } catch (e) {
      // For debugging
      print(e);
      // For example purpose we do not have a message
      emit(GenericState(isFailed: true));
    }
  }
}

///
/// Recent categories Cubit
///
class RecentCategoriesCubit extends Cubit<GenericState> {
  ///
  /// Repository dependency
  ///
  final Repository repository;

  ///
  /// Cubit constructor. Send a loading state as default.
  ///
  RecentCategoriesCubit({@required this.repository})
      : super(GenericState(isLoading: true));

  // ==================================
  // EVENTS
  // ==================================

  ///
  /// Load data from repository
  ///
  void load() async {
    //#log
    print("[EVENT] Recent Categories :: Load");

    // Every time when try to load data from repository put the application
    // in a loading state
    emit(GenericState(isLoading: true));

    try {
      // Wait for data from repository
      List categories = await this.repository.getData("recent");

      // Send a success state
      emit(GenericState(categories: categories, isFailed: false));
    } catch (e) {
      // For debugging
      print(e);
      // For example purpose we do not have a message
      emit(GenericState(isFailed: true));
    }
  }
}

///
/// Upcoming categories Cubit
///
class UpcomingCategoriesCubit extends Cubit<GenericState> {
  ///
  /// Repository dependency
  ///
  final Repository repository;

  ///
  /// Cubit constructor. Send a loading state as default.
  ///
  UpcomingCategoriesCubit({@required this.repository})
      : super(GenericState(isLoading: true));

  // ==================================
  // EVENTS
  // ==================================

  ///
  /// Load data from repository
  ///
  void load() async {
    //#log
    print("[EVENT] Upcoming Categories :: Load");

    // Every time when try to load data from repository put the application
    // in a loading state
    emit(GenericState(isLoading: true));

    try {
      // Wait for data from repository
      List categories = await this.repository.getData("upcoming");

      // Send a success state
      emit(GenericState(categories: categories, isFailed: false));
    } catch (e) {
      // For debugging
      print(e);
      // For example purpose we do not have a message
      emit(GenericState(isFailed: true));
    }
  }
}

只需將代碼復制並粘貼到 main.dart 中即可查看結果。 我嘗試盡可能多地注釋代碼以幫助您理解。

我也推薦BLOC 從零到英雄教程。 它將對您理解 BLoC 庫並正確使用它有很大幫助。


更新 1

每次更改選項卡時重新加載數據

要在每次更改 Tab 時重新加載數據,您可以使用 TabBar 中的onTap ,如下所示。

TabBar(
  onTap: (tabIndex) {
    switch (tabIndex) {
      // Popular
      case 0:
        BlocProvider.of<PopularCategoriesCubit>(context).load();
        break;

      // Recent
      case 1:
        BlocProvider.of<RecentCategoriesCubit>(context).load();
        break;

      // Upcoming
      case 2:
        BlocProvider.of<UpcomingCategoriesCubit>(context).load();
        break;
    }
  },
  tabs: [
    Tab(text: "Popular"),
    Tab(text: "Recent"),
    Tab(text: "Upcoming"),
  ],
),

注意:現在您不必在創建最近的和即將到來的肘部(非默認選項卡)時發出load() ) - 因為 Tab tap 會處理這些。

BlocProvider<RecentCategoriesCubit>(
  create: (context) =>
      RecentCategoriesCubit(repository: repository),
),
BlocProvider<UpcomingCategoriesCubit>(
  create: (context) =>
      UpcomingCategoriesCubit(repository: repository),
),

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM