I'm new to Flutter Redux, I got a problem and I have no idea how to deal with it at all! I extracted the main code to keep this simple - tap indicators to switch PageView, scroll PageView to synchronise the indicator. Here is my code:
app state:
class AppState {
final List menuList;
final int currentIndex;
AppState({this.menuList, this.currentIndex});
}
the reducers:
AppState appReducer(AppState state, Object action) {
return AppState(
menuList: menuListReducer(state.menuList, action),
currentIndex: currentIndexReducer(state.currentIndex, action));
}
final menuListReducer = combineReducers<List>(
[TypedReducer<List, SetMenuListAction>(_setMenuList)]);
List _setMenuList(List menuList, SetMenuListAction action) {
menuList = action.menuList;
return menuList;
}
final currentIndexReducer = combineReducers<int>(
[TypedReducer<int, SetCurrentIndexAction>(_setCurrentIndex)]);
int _setCurrentIndex(int currentIndex, SetCurrentIndexAction action) {
currentIndex = action.index;
return currentIndex;
}
the action:
class SetMenuListAction {
List menuList;
SetMenuListAction(this.menuList);
}
class SetCurrentIndexAction {
int index;
SetCurrentIndexAction(this.index);
}
the main logic:
void main() {
final store = Store<AppState>(
appReducer,
initialState: AppState(menuList: [
{
'picUrl': 'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'description': 'this is the first image'
},
{
'picUrl': 'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
'description': 'this is the second image'
},
{
'picUrl':
'http://img4.imgtn.bdimg.com/it/u=3434394339,2114652299&fm=214&gp=0.jpg',
'description': 'this is the third image'
},
{
'picUrl': 'http://pic1.win4000.com/pic/2/07/8c57e143b1.jpg',
'description': 'this is the fourth image'
},
], currentIndex: 0),
);
runApp(App(
store: store,
));
}
// App
class App extends StatelessWidget {
final Store<AppState> store;
const App({Key key, this.store}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(title: 'Flutter redux example', home: MyDetail()),
);
}
}
class MyDetail extends StatefulWidget {
#override
_MyDetailState createState() => _MyDetailState();
}
class _MyDetailState extends State<MyDetail> with TickerProviderStateMixin {
PageController _controller;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, int>(
converter: (store) => store.state.currentIndex,
onDidChange: (newIdx) {
//this won't work because the _controller hasn't been attached to PageView
_controller.jumpToPage(newIdx);
},
builder: (BuildContext context, int idx) {
return StoreConnector<AppState, List>(
converter: (store) => store.state.menuList,
onDidChange: (newList) {
//maybe do something further
},
builder: (BuildContext context, List menus) {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
//pageview
Expanded(
child: PageView(
children: menus.map((item) {
return Column(
children: <Widget>[
Image.network(item['picUrl']),
Text(
item['description'],
style: TextStyle(fontSize: 24.0),
)
],
);
}).toList(),
onPageChanged: (int index) {
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(index));
},
physics: BouncingScrollPhysics(),
),
),
//indicators
Container(
margin: EdgeInsets.only(bottom: 50.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: menus
.asMap()
.map((i, item) => MapEntry(
i,
GestureDetector(
onTap: () {
//this won't work either maybe because the widgets is rebuilding
_controller.jumpToPage(i);
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(i));
},
child: Container(
width: 10.0,
height: 10.0,
color: i == idx
? Colors.purpleAccent
: Colors.blue,
margin: EdgeInsets.only(right: 10.0),
),
)))
.values
.toList(),
),
)
],
),
);
},
);
},
);
}
}
Sorry for the long code, but I think maybe this can help to understand my problem:
When I tap the indicator, I want to synchronise the PageView, that is _controller.jumpToPage(i), but it will show Errors. So how to make this work?
I can change the currentIndex in another screen, how to synchronise the PageView?
Is there any method to watch the state changes(separately, not the whole state) and do something?
After debugging your code I found that you are missing controller: _controller in PageView, this should fix it:
Expanded(
child: PageView(
controller: _controller,
children: menus.map((item) {
return Column(
children: <Widget>[
Image.network(item['picUrl']),
Text(
item['description'],
style: TextStyle(fontSize: 24.0),
)
],
);
}).toList(),
onPageChanged: (int index) {
StoreProvider.of<AppState>(context)
.dispatch(SetCurrentIndexAction(index));
},
physics: BouncingScrollPhysics(),
),
),
Related
This is my code:
class MobileHomePage extends StatefulWidget {
const MobileHomePage({Key? key}) : super(key: key);
#override
State<MobileHomePage> createState() => _MobileHomePageState();
}
class _MobileHomePageState extends State<MobileHomePage> {
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
int? second;
#override
void initState() {
second = int.parse(DateFormat.s().format(DateTime.now()));
Timer.periodic(const Duration(seconds: 1), (timer) => getCurrentTime());
super.initState();
}
void getCurrentTime() {
setState(() {
second = int.parse(DateFormat.s().format(DateTime.now()));
});
}
User user = FirebaseAuth.instance.currentUser!;
final Stream<QuerySnapshot> _mainSubjectStream = FirebaseFirestore.instance
.collection('systems')
.orderBy('system_name', descending: false)
.snapshots();
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
final GlobalKey<ScaffoldState> _key = GlobalKey();
return Scaffold(
key: _key,
body: SafeArea(
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Container(
color: Colors.transparent,
child: StreamBuilder<QuerySnapshot>(
stream: _mainSubjectStream,
builder: (context, mainSubjectSnapshot) {
if (mainSubjectSnapshot.hasError) {
return const Center(child: Text('Error));
}
if (mainSubjectSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
var length = mainSubjectSnapshot.data!.docs.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: length,
itemBuilder: (context, i) {
var output = mainSubjectSnapshot.data!.docs[i];
return Text(output['name']);
});
},
),
)),
const Spacer(flex: 3),
Expanded(
flex: 5,
child: DatePicker(
DateTime.now(),
initialSelectedDate: _date,
daysCount: 8,
locale: 'pt',
selectionColor: const Color.fromRGBO(67, 97, 238, 1),
selectedTextColor: Colors.white,
onDateChange: (date){
setState(() {
_date = date;
});
},
),
),
Expanded(
flex: 1,
child: Text(second.toString()
),
],
),
));
}
}
When I try to display the clock (as in second.toString()), the StreamBuilder is also updated, resulting in a new progress indicator each second. What is breaking my head is that I just copied the same strutted that I used in other code – which works fine. Did I do something wrong? Why is it influencing the other stream?
EDIT
Have just found out that the error is related to the key in the scaffold. When I deleted it, it worked fine. The thing is that I need I as I have a custom button to open the drawer. 1st question is how does it influences it. 2nd question is how to solve it?
So, I'm trying to display an Item on my app using the bloc pattern but I'm getting this error:
The following LateError was thrown building ItemDetailsScreen(state: _ItemDetailsScreenState#55c51):
LateInitializationError: Field 'item' has not been initialized.
The relevant error-causing widget was:
ItemDetailsScreen
ItemDetailsScreen:file:/mobile/lib/src/autoroute/app_router.gr.dart:70:40
Below the app_router.dart
AutoRoute(
path: '/item/:itemId',
page: ItemDetailsScreen,
name: 'ItemDetailsRoute',
meta: {'hideBottomNav': true},
guards: [AuthGuard],
),
and the app_router.gr.dart
ItemDetailsRoute.name: (routeData) {
final pathParams = routeData.inheritedPathParams;
final args = routeData.argsAs<ItemDetailsRouteArgs>(
orElse: () =>
ItemDetailsRouteArgs(itemId: pathParams.getInt('itemId')));
return MaterialPageX<dynamic>(
routeData: routeData, child: ItemDetailsScreen(args.itemId));
},
And view code in itemDetails.dart file as bellow
class ItemDetailsScreen extends StatefulWidget {
final int itemId;
const ItemDetailsScreen(#pathParam this.itemId, {Key? key}) : super(key: key);
#override
_ItemDetailsScreenState createState() => _ItemDetailsScreenState();
}
class _ItemDetailsScreenState extends State<ItemDetailsScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
//final auth = Provider.of<KeycloakAuth>(context);
return BlocBuilder<ItemBloc, ItemState>(builder: (context, state) {
if (state is ItemLoadingState) {
return const MyCircularProgressIndicator();
}
if (state is ItemLoadedState) {
Item showItem = state.item;
return Scaffold(
appBar: AppBar(
title: Text(showItem.name),
),
body: ListView(
children: [
CarouselSlider(
options: CarouselOptions(
height: 300.0,
enableInfiniteScroll: false,
aspectRatio: 16 / 10,
viewportFraction: 1.0,
),
items: showItem.pictures.whereNotNull().map((String e) {
return CachedNetworkImage(
imageUrl: "${e}_SMALL.jpg",
placeholder: (context, url) =>
const CircularProgressIndicator(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
);
}).toList()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
children: [
const SizedBox(
height: 15,
),
Text(
showItem.name,
style: MyTheme.bigBlack,
),
const SizedBox(
height: 15,
),
],
),
)
],
),
);
}
if (state is ItemLoadingErrorState) {
return Center(
child: Text(state.error.toString()),
);
}
return Container();
});
}
}
Any idea how I can fix it ? thank you in advance for your answers.
in file itemDetails.dart you declared final int itemId; inside class ItemDetailsScreen
when you called ItemDetailsScreen in AutoRoute you did not pass a value for final int itemId;
When you write your code in null safety you should declare something to the variable's .So declare some values to final int itemid
I have multiple widget and lists within CustomScrollView and I would like to stop CustomScrollView to scroll while scrolling on some pixels bound condition.
I can use NeverScrollPhysics() to stop it but I don't want to use setState() function here because the CustomScrollview content with lists is big enough to make the screen laggy while reloading on scroll.
Also tried with Provider but the builder is providing only a child widget which is not working with sliver list.
Here is the code using setState() :
NotificationListener(
onNotification: (ScrollNotification notif) {
if(notif is ScrollUpdateNotification) {
if (canScroll && notif.metrics.pixels > 100) {
canScroll = false;
setState(() {});
}
}
if(notif is ScrollEndNotification) {
if(!canScroll) {
canScroll = true;
setState(() {});
}
}
return true;
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(),
List(),
List(),
],
),
),
Is there a way to reload only the CustomScrollView without its child ? Otherwise any workaround to prevent scrolling in this case ?
Thanks for help
When the build method is called, all widgets in that build method will be rebuild except for const widgets, but const widget cannot accept dynamic arguments (only a constant values).
Riverpod provides a very good solution in this case,
With ProviderScope you can pass arguments by inherited widget instead of widget constructor (as when passing arguments using navigation) so the contractor can be const.
Example :
Data module
TLDR you need to use Freezed package or override the == operator and the hashCode almost always because of dart issue.
class DataClass {
final int age;
final String name;
const DataClass(this.age, this.name);
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is DataClass && other.age == age && other.name == name;
}
#override
int get hashCode => age.hashCode ^ name.hashCode;
}
setting our ScopedProvider as a global variable
final dataClassScope = ScopedProvider<DataClass>(null);
The widget we use in our list
class MyChildWidget extends ConsumerWidget {
const MyChildWidget();
#override
Widget build(BuildContext context, ScopedReader watch) {
final data = watch(dataClassScope);
// Note for better optimaization
// in case you are sure the data you are passing to this widget wouldn't change
// you can just use StatelessWidget and set the data as:
// final data = context.read(dataClassScope);
// use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change
print('widget with name\n${data.name} rebuild');
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Text(
'Name : ${data.name}\nAge ${data.age}',
textAlign: TextAlign.center,
),
),
);
}
}
finally the main CustomScrollView widget
class MyMainWidget extends StatefulWidget {
const MyMainWidget();
#override
State<MyMainWidget> createState() => _MyMainWidgetState();
}
class _MyMainWidgetState extends State<MyMainWidget> {
bool canScroll = true;
void changeCanScrollState() {
setState(() => canScroll = !canScroll);
print('canScroll $canScroll');
}
final dataList = List.generate(
20,
(index) => DataClass(10 * index, '$index'),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
changeCanScrollState();
},
child: CustomScrollView(
shrinkWrap: true,
physics: canScroll
? BouncingScrollPhysics()
: NeverScrollableScrollPhysics(),
slivers: [
for (int i = 0; i < dataList.length; i++)
ProviderScope(
overrides: [
dataClassScope.overrideWithValue(dataList[i]),
],
child: const MyChildWidget(),
),
],
),
),
);
}
}
Don't forget to wrap the MaterialApp with ProviderScope.
runApp(
ProviderScope(
child: MyApp(),
),
);
Try this solution use const constructor for child widget so it won't rebuild unless widget changed
class MyHomePage extends StatelessWidget {
ValueNotifier<ScrollPhysics> canScroll =
ValueNotifier(const BouncingScrollPhysics());
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (canScroll.value.runtimeType == BouncingScrollPhysics &&
notif.metrics.pixels > 100) {
canScroll.value = const NeverScrollableScrollPhysics();
debugPrint("End false");
}
}
if (notif is ScrollEndNotification) {
if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
debugPrint("End");
Future.delayed(const Duration(milliseconds: 300),
() => canScroll.value = const BouncingScrollPhysics());
debugPrint("End1");
}
}
return true;
},
child: ValueListenableBuilder(
valueListenable: canScroll,
builder:
(BuildContext context, ScrollPhysics scrollType, Widget? child) =>
CustomScrollView(
physics: scrollType,
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
color: Colors.black,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Container(
height: 100,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
Container(
height: 200,
color: Colors.grey,
),
Container(
height: 200,
color: Colors.blue,
),
],
),
),
],
),
),
),
);
}
}
Are you just need to stop the user from scrolling it? I think you can try to controller the list to a fixed position by using jumoTo.
...
final _controller = ScrollController();
#override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notif) {
if (notif is ScrollUpdateNotification) {
if (notif.metrics.pixels > 100) {
_controller.jumpTo(100)
}
}
return true;
},
child: CustomScrollView(
controller: _controller,
...
I have Form screen that contains a form widget.
After changing state (now with bloc but I tested with setState, no different) I got following error:
The following NoSuchMethodError was thrown while handling a gesture:
The method 'call' was called on null.
Receiver: null
Tried calling: call()
This only happened when I change state (if I don't yield new state, or setState it works without error).
but after changing state and probably rebuilding widget I got error:
This is main screen:
class _AuthScreenState extends State<AuthScreen> {
final AuthRepository repository = AuthRepository();
PageController controller;
Bloc _bloc;
#override
void initState() {
controller = PageController(initialPage: widget.page);
super.initState();
}
void changePage(int page) {
controller.animateToPage(
page,
curve: Curves.ease,
duration: Duration(milliseconds: 300),
);
}
void onSubmit(AuthType authType, AuthReq req) {
if (authType == AuthType.LOGIN) {
_bloc.add(LoginEvent(req: req));
} else {
_bloc.add(RegisterEvent(req: req));
}
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (ctx) => AuthBloc(repository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
_bloc = context.bloc<AuthBloc>();
return ScreenContainer(
loading: state is LoadingState,
child: Container(
width: double.infinity,
height: double.infinity,
child: Column(
children: [
Expanded(
child: PageView.builder(
controller: controller,
physics: NeverScrollableScrollPhysics(),
itemCount: 2,
itemBuilder: (context, position) {
return position == 0
? LoginPage(
onPageChange: () => changePage(1),
onSubmit: (req) => onSubmit(AuthType.LOGIN, req),
)
: RegisterPage(
onPageChange: () => changePage(0),
onSubmit: (req) => onSubmit(AuthType.REGISTER, req),
);
},
),
),
],
),
),
);
},
),
);
}
}
class LoginPage extends StatelessWidget {
final VoidCallback onPageChange;
final void Function(AuthReq req) onSubmit;
final FormController controller = FormController();
LoginPage({
#required this.onPageChange,
#required this.onSubmit,
});
void submit() {
var values = controller?.submit();
if (values.isNull) {
return;
}
onSubmit(AuthReq(password: values['password'], username: values['email']));
}
#override
Widget build(BuildContext context) {
var authType = AuthType.LOGIN;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: hP),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormWrapper(
inputs: loginFields,
controller: controller,
),
submitButton(context, authType, submit),
],
),
),
],
),
),
),
],
);
}
}
class FormController {
Map Function() submit;
}
class FormWrapper extends StatefulWidget {
final List<InputProps> inputs;
final FormController controller;
FormWrapper({
#required this.inputs,
this.controller,
});
#override
_FormWrapperState createState() => _FormWrapperState(controller);
}
class _FormWrapperState extends State<FormWrapper> {
final _formKey = GlobalKey<FormState>();
_FormWrapperState(FormController _controller) {
_controller.submit = submit;
}
bool _autoValidation = false;
Map values = {};
void setValue(String key, dynamic value) {
values[key] = value;
}
Map submit() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
return values;
} else {
setState(() {
_autoValidation = true;
});
return null;
}
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
autovalidate: _autoValidation,
key: _formKey,
child: Column(
children: widget.inputs
.map(
(e) => Container(
margin: EdgeInsets.only(bottom: e.isLast ? 0 : 24.0 - 7.0),
child: RoundedInput(
inputProps: e,
onChange: (value) => setValue(e.label, value),
),
),
)
.toList(),
),
),
);
}
}
I found solution, I post answer instead of deleting question for may help others in future :)
I override didUpdateWidget to reinitialize variable in _FormWrapperState:
#override
void didUpdateWidget(oldWidget) {
widget.controller.submit = submit;
super.didUpdateWidget(oldWidget);
}
I have a rather complex situation in a Flutter App.
I have a Home screen that is a swipable PageView,that displays 3 child Widgets : Calendar, Messages, Profile.
My issue at the moment is with the Calendar Widget. It is populated dynamically from the initState() method.
I managed to fix a first issue that came from swiping from one page to another that caused rebuilding the Calendar Widget every time.
My issue now is when I tap an item in the Calendar list, I open the detail view. Then, when I close it… all is still OK. However, when I swipe again the initState() method is called once more and the List view is rebuilt. I would like to prevent that and preserve it's state. any suggestions ?
Here is the Home code.
class HomeStack extends StatefulWidget {
final pages = <HomePages> [
CalendarScreen(),
MessagesScreen(),
ProfileScreen(),
];
#override
_HomeStackState createState() => _HomeStackState();
}
class _HomeStackState extends State<HomeStack> with AutomaticKeepAliveClientMixin<HomeStack> {
User user;
#override
bool get wantKeepAlive{
return true;
}
#override
void initState() {
print("Init home");
_getUser();
super.initState();
}
void _getUser() async {
User _user = await HomeBloc.getUserProfile();
setState(() {
user = _user;
});
}
final PageController _pageController = PageController();
int _selectedIndex = 0;
void _onPageChanged(int index) {
_selectedIndex = index;
}
void _navigationTapped(int index) {
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 300),
curve: Curves.ease
);
}
GestureDetector _navBarItem({int pageIndex, IconData iconName, String title}) {
return GestureDetector(
child: HomeAppBarTitleItem(
index: pageIndex,
icon: iconName,
title: title,
controller: _pageController
),
onTap: () => _navigationTapped(pageIndex),
);
}
Widget _buildWidget() {
if (user == null) {
return Center(
child: ProgressHud(imageSize: 70.0, progressSize: 70.0, strokeWidth: 5.0),
);
} else {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_navBarItem(
pageIndex: 0,
iconName: Icons.calendar_today,
title: AppLocalizations.of(context).calendarViewTitle,
),
_navBarItem(
pageIndex: 1,
iconName: Icons.message,
title: AppLocalizations.of(context).messagesViewTitle,
),
_navBarItem(
pageIndex: 2,
iconName: Icons.face,
title: AppLocalizations.of(context).profileViewTitle,
),
],
),
backgroundColor: Colors.transparent,
elevation: 0.0,
),
backgroundColor: Colors.transparent,
body: PageView(
onPageChanged: (index) => _onPageChanged(index),
controller: _pageController,
children: widget.pages,
),
floatingActionButton: widget.pages.elementAt(_selectedIndex).fabButton,
);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Stack(
children: <Widget>[
BackgroundGradient(),
_buildWidget(),
],
),
onWillPop: () async {
return true;
},
);
}
}
And the Calendar code.
class CalendarScreen extends StatelessWidget implements HomePages {
/// TODO: Prevent reloading
/// when :
/// 1) push detail view
/// 2) swipe pageView
/// 3) come back to calendar it reloads
static const String routeName = "/calendar";
static Color borderColor(EventPresence status) {
switch (status) {
case EventPresence.present:
return CompanyColors.grass;
case EventPresence.absent:
return CompanyColors.asher;
case EventPresence.pending:
return CompanyColors.asher;
default:
return CompanyColors.asher;
}
}
final FloatingActionButton fabButton = FloatingActionButton(
onPressed: () {}, /// TODO: Add action to action button
backgroundColor: CompanyColors.sky,
foregroundColor: CompanyColors.snow,
child: Icon(Icons.add),
);
#override
Widget build(BuildContext context) {
return CalendarProvider(
child: CalendarList(),
);
}
}
class CalendarList extends StatefulWidget {
#override
_CalendarListState createState() => _CalendarListState();
}
class _CalendarListState extends State<CalendarList> with AutomaticKeepAliveClientMixin<CalendarList> {
Events events;
void _getEvents() async {
Events _events = await CalendarBloc.getAllEvents();
setState(() {
events = _events;
});
}
#override
void initState() {
_getEvents();
super.initState();
}
#override
bool get wantKeepAlive{
return true;
}
Widget _displayBody() {
if (events == null) {
return ProgressHud(imageSize: 30.0, progressSize: 40.0, strokeWidth: 3.0);
} else if(events.future.length == 0 && events.past.length == 0) return _emptyStateView();
return EventsListView(events: events);
}
#override
Widget build(BuildContext context) {
return _displayBody();
}
Widget _emptyStateView() {
return Center(
child: Text("No data"),
);
}
}
class EventsListView extends StatefulWidget {
final Events events;
EventsListView({this.events});
#override
_EventsListViewState createState() => _EventsListViewState();
}
class _EventsListViewState extends State<EventsListView> {
GlobalKey _pastEventsScrollViewKey = GlobalKey();
GlobalKey _scrollViewKey = GlobalKey();
double _opacity = 0.0;
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderSliverList renderSliver = _pastEventsScrollViewKey.currentContext.findRenderObject();
setState(() {
CustomScrollView scrollView = _scrollViewKey.currentContext.widget;
scrollView.controller.jumpTo(renderSliver.geometry.scrollExtent);
_opacity = 1.0;
});
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: AnimatedOpacity(
opacity: _opacity,
duration: Duration(milliseconds: 300),
child: CustomScrollView(
key: _scrollViewKey,
controller: ScrollController(
//initialScrollOffset: initialScrollOffset,
keepScrollOffset: true,
),
slivers: <Widget>[
SliverList(
key: _pastEventsScrollViewKey,
delegate: SliverChildBuilderDelegate( (context, index) {
Event event = widget.events.past[index];
switch (event.type) {
case EventType.competition:
return CompetitionListItem(event: event);
case EventType.training:
return TrainingListItem(event: event);
case EventType.event:
return EventListItem(event: event);
}
},
childCount: widget.events.past.length,
),
),
SliverList(
delegate: SliverChildBuilderDelegate( (context, index) {
return Padding(
padding: EdgeInsets.only(top: 32.0, left: 16.0, right: 16.0, bottom: 16.0),
child: Text(
DateFormat.MMMMEEEEd().format(DateTime.now()),
style: Theme.of(context).textTheme.body2.copyWith(
color: CompanyColors.snow,
),
),
);
},
childCount: 1,
),
),
SliverList(
delegate: SliverChildBuilderDelegate( (context, index) {
Event event = widget.events.future[index];
switch (event.type) {
case EventType.competition:
return CompetitionListItem(event: event);
case EventType.training:
return TrainingListItem(event: event);
case EventType.event:
return EventListItem(event: event);
}
},
childCount: widget.events.future.length,
),
),
],
),
),
);
}
}
From the documentation on AutomaticKeepAliveClientMixin:
/// A mixin with convenience methods for clients of
[AutomaticKeepAlive]. Used with [State] subclasses.
/// Subclasses must implement [wantKeepAlive], and their [build]
methods must call super.build (the return value will always return
null, and should be ignored).
So in your code, before you return the Scaffold just call super.build:
Widget build(BuildContext context) {
super.build(context);
return Scaffold(...);
}