[英]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 庫並正確使用它有很大幫助。
要在每次更改 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.