How do I pass state to BottomNavigationBar pages in Flutter with redux?
Answer a question
So normally I would have have a list of widgets and a current index indicator inside my homepage widget whenever using a bottom navigation bar. Or at least that's what I've seen most basic tutorials use.
body: _widgets.elementAt(_currentIndex)
onTap: (index) {
setState(() {
_currentIndex = index;
}
}
I want to transition to a way of managing state with redux now though, and I'm not exactly sure how to handle this change within my home widget.
The body field of my Scaffold now becomes
body: StoreConnector<AppState, AppViewModel>(
converter: (Store<AppState> store) => AppViewModel.create(store),
builder: (BuildContext context, AppViewModel viewModel) => // I should have the default page here
)
Each page widget should take the view model as a parameter.
// page widget example
class BrowsePage extends StatefulWidget {
final String title = "Browse recipes";
final AppViewModel model;
BrowsePage(this.model);
@override
_BrowseState createState() => _BrowseState();
}
So I don't know how to organize my widget list this way. I can no longer have a widget list during initialization since I don't have access to the view model there (or at least I don't think I do). I also don't I can access the view model in the onTap callback.
So how should I go about this?
Answers
Here is how you can manage BottomNavigationBar with Redux.
You need a model, state, action & reducer for managing your tabs.
Let's assume the following:
Model - TabModel
State - TabState
Action - ChangeTabAction
Reducer - tabReducer
Create a TabModel class to customize your BottomNavigationBar items like title, icon, color, etc.
TabModel
class TabModel {
final String title;
final String iconPath; // If you are using Material icons, then use IconData
final Color color;
const TabModel({
@required this.title,
@required this.iconPath,
@required this.color,
});
}
Create a TabState which will store the current active index with the default index set to 0.
TabState
import 'dart:convert';
import 'package:flutter/material.dart';
@immutable
class TabState {
final int currentIndex;
const TabState({this.currentIndex});
factory TabState.initial() => const TabState(currentIndex: 0);
TabState copyWith({int currentIndex}) {
return TabState(
currentIndex: currentIndex ?? this.currentIndex,
);
}
dynamic toJson() => {
'currentIndex': currentIndex,
};
@override
String toString() {
return '${const JsonEncoder.withIndex(' ').convert(this)}';
}
}
Create TabAction which will take in the index position of the new tab. We will use this action to switch tabs.
TabAction
class ChangeTabAction {
const ChangeTabAction(this.index);
final int index;
}
Create TabReducer that will handle the dispatched actions & will change the TabState.
TabReducer
final tabReducer = TypedReducer(_tabActionReducer);
TabState _tabActionReducer(TabState state, dynamic action) {
if (action is ChangeTabAction) {
return changeTab(state, action);
}
return state;
}
TabState changeTab(TabState state, dynamic action) {
return state.copyWith(
currentIndex: action.index,
);
}
Pass this tabReducer to your main reducer, in my case, it is appReducer.
AppState appReducer(AppState state, dynamic action) {
return state.copyWith(
tabState: tabReducer(state.tabState, action),
..., // Other reducers
);
}
Your AppState should look something like this:
import 'dart:convert';
import 'package:flutter/material.dart';
class AppState {
final TabState tabState;
... // Other states
const AppState({
@required this.tabState,
.. // other states
});
factory AppState.initial() {
return AppState(
tabState: TabState.initial(),
);
}
AppState copyWith({
TabState tabState,
}) {
return AppState(
tabState: tabState ?? this.tabState,
);
}
dynamic toJson() => {
'tabState': tabState,
};
@override
String toString() {
return '${const JsonEncoder.withIndent(' ').convert(this)}';
}
}
Now, we'll create our UI & will handle the BottomNavigationBar as follows:
Let's suppose your BottomNavigationBar is in HomeWidget.
class HomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector(
converter: (Store<AppState> store) => AppViewModel.create(store),
builder: (BuildContext context, AppViewModel viewModel) {
return Scaffold(
body: IndexedStack(
index: viewModel.tabState.currentIndex,
children: [
Dashboard(), // Replace these widgets with your widgets
SomeOtherWidget(),
Settings(),
MyProfile(),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: viewModel.tabState.currentIndex,
type: BottomNavigationBarType.fixed,
items: viewModel.buildItems(),
onTap: viewModel.changeTab,
),
);
},
);
}
}
My viewModel looks something like this. You might need to modify your's AppViewModel.
class AppViewModel {
final TabState tabState;
final void Function(int) changeTab;
const AppViewModel({
@required this.tabState,
@required this.changeTab,
});
static AppViewModel create(Store<AppState> store) {
return AppViewModel(
tabState: store.state.tabState,
changeTab: (index) => store.dispatch(
ChangeTabAction(index),
),
);
}
List<BottomNavigationBarItem> buildItems() {
return [
BottomNavigationBarItem(
icon: SvgPicture.asset(Assets.svg.home,
color: tabState.currentIndex == 0 ? primaryColor : neutralColor,
),
title: const Text(Strings.home),
),
BottomNavigationBarItem(
icon: SvgPicture.asset(Assets.svg.other,
color: tabState.currentIndex == 1 ? primaryColor : neutralColor,
),
title: const Text(Strings.other),
),
BottomNavigationBarItem(
icon: SvgPicture.asset(Assets.svg.settings,
color: tabState.currentIndex == 2 ? primaryColor : neutralColor,
),
title: const Text(Strings.settings),
),
BottomNavigationBarItem(
icon: SvgPicture.asset(Assets.svg.myProfile,
color: tabState.currentIndex == 3 ? primaryColor : neutralColor,
),
title: const Text(Strings.myProfile),
),
];
}
}
更多推荐
所有评论(0)