Flutter: drawer becomes unavailable when pushing subviews with Navigator - flutter

Link to source on github: https://github.com/dnn1s/flutter_navigationtest
I want to accomplish the same navigation approach as Google does with the Play Store app: the drawer lists the available "root" views (in my case, view1 to view3), while any root view can have any number of subviews and its own navigation stack. The catch is: even when the user is on one of the subviews, the drawer is still accessible by using a swipe gesture, starting from the outer left of screen and going to the center - and this is not possible with my current approach. As for the Play Store app, when you tap on an app to see its details, you can either go back by tapping the arrow on the upper left OR directly invoke the drawer by swiping.
main.dart: nothing fancy
void main() => runApp(new NavigationTestApp());
class NavigationTestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation test',
home: new MainPage(),
);
}
}
mainpage.dart: this view includes the drawer and its items
import 'package:flutter/material.dart';
import 'view1.dart';
import 'view2.dart';
import 'view3.dart';
/// just a wrapper class for drawer items; in my original code, these include
/// icons and other properties
class DrawerItem {
String title;
DrawerItem({this.title});
}
class MainPage extends StatefulWidget {
/// list of items in the drawer
final drawerItems = [
new DrawerItem(title: 'Item 1'),
new DrawerItem(title: 'Item 2'),
new DrawerItem(title: 'Item 3')
];
#override
State<MainPage> createState() => new MainPageState();
}
class MainPageState extends State<MainPage> {
int _selectedPageIndex = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
/// dynamic title, depending on the current view
title: new Text(widget.drawerItems[_selectedPageIndex].title),
),
drawer: new Drawer(
child: new ListView(
padding: EdgeInsets.zero,
children: <Widget>[
new DrawerHeader(child: new Text('Drawer header')),
/// quick and easy way to create the items;
/// in the original
/// code, these items are built in a loop
_buildDrawerItem(0),
_buildDrawerItem(1),
_buildDrawerItem(2),
],
)
),
body: _buildCurrentPage()
);
}
Widget _buildCurrentPage() {
switch(_selectedPageIndex) {
case 0: return new View1();
case 1: return new View2();
case 2: return new View3();
}
return new Text('Invalid page index');
}
Widget _buildDrawerItem(int index) {
return new ListTile(
title: new Text(widget.drawerItems[index].title),
selected: _selectedPageIndex == index,
onTap: () => _handleSelection(index),
);
}
void _handleSelection(int index) {
setState(() {
_selectedPageIndex = index;
});
/// close the drawer
Navigator.of(context).pop();
}
}
This is the first root view:
view1.dart:
class View1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('Push Subview1'),
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (context) {
return new SubView1();
}
));
},
);
}
}
subview1.dart:
import 'package:flutter/material.dart';
class SubView1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SubView1'),
),
body: new Text('Pushed from View1')
);
}
}
I know about the Cupertino classes, but I prefer the material design. Can someone point me in the right direction on how to implement the desired behaviour?

Since they are two different pages, drawer in one page will not be available in another page. If you do want that you should create drawer in both pages. I've wrapped everything as much as i can regarding Drawer inside a separate class called drawer.dart with comments everywhere so that it would be helpful to understand.
Plugin used
scoped_model: "^0.2.0"
mainpage.dart
import 'package:commo_drawer/drawer.dart';
import 'package:flutter/material.dart';
import 'view1.dart';
import 'view2.dart';
import 'view3.dart';
MainPageState mainPageState = new MainPageState();
class MainPage extends StatefulWidget {
#override
State<MainPage> createState() => mainPageState;
}
class MainPageState extends State<MainPage> {
MyDrawer myDrawer;
#override
void initState() {
myDrawer =
new MyDrawer(shouldRebuildState: DrawerItemClick.NEED_TO_REBUILD_STATE);
myDrawer.addListener(() {
setState(() {});
});
super.initState();
}
rebuild() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(drawerItems[myDrawer.selectedPageIndex].title),
),
drawer: myDrawer.buildDrawer(context),
body: _buildCurrentPage(),
);
}
Widget _buildCurrentPage() {
switch (myDrawer.selectedPageIndex) {
case 0:
return new View1();
case 1:
return new View2();
case 2:
return new View3();
}
return new Text('Invalid page index');
}
}
subview1.dart
import 'package:commo_drawer/drawer.dart';
import 'package:flutter/material.dart';
class SubView1 extends StatelessWidget {
final MyDrawer myDrawer = new MyDrawer(shouldRebuildState: DrawerItemClick.NEED_NOT_REBUILD_STATE);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SubView1'),
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context).pop();
}),
),
body: new Text('Pushed from View1'),
drawer: myDrawer.buildDrawer(context),
);
}
}
drawer.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
int _selectedPageIndex = 0;
BuildContext _context;
class MyDrawer extends Model {
DrawerItemClick shouldRebuildState;
MyDrawer({this.shouldRebuildState});
int get selectedPageIndex => _selectedPageIndex;
Drawer buildDrawer(BuildContext context) {
_context = context;
return new Drawer(
child: new ListView(
padding: EdgeInsets.zero,
children: <Widget>[
new DrawerHeader(child: new Text('Drawer header')),
buildDrawerItem(0),
buildDrawerItem(1),
buildDrawerItem(2),
],
),
);
}
Widget buildDrawerItem(int index) {
return new ListTile(
title: new Text(drawerItems[index].title),
selected: selectedPageIndex == index,
onTap: () => _handleSelection(index),
);
}
void _handleSelection(int index) {
Navigator.of(_context).pop(); // Close drawer
if (shouldRebuildState == DrawerItemClick.NEED_TO_REBUILD_STATE) {
if (_selectedPageIndex != index) {
_selectedPageIndex = index;
notifyListeners();
}
} else {
shouldRebuildState = DrawerItemClick.NEED_TO_REBUILD_STATE;
Navigator.of(_context).pop(); // Close SubView
if (_selectedPageIndex != index) {
_selectedPageIndex = index;
notifyListeners();
}
}
}
}
final drawerItems = [
new DrawerItem(title: 'Item 1'),
new DrawerItem(title: 'Item 2'),
new DrawerItem(title: 'Item 3'),
];
class DrawerItem {
String title;
DrawerItem({this.title});
}
enum DrawerItemClick { NEED_TO_REBUILD_STATE, NEED_NOT_REBUILD_STATE }

Related

How to close all other expansion tile exept one

I have a drawer that have a listview that have a nested expansion tiles as its children.
1- I want to close all open expanded tiles exepet the one that just opened.(No more the 1 expanded tile that is open)
What is the best way to do this?
2- i also want to keep the open one stay open when i close and reopen the drawer (I acheved this by using key:PageStorageKey but if there is a better way i would like to hear it).
I solved the problem by using this custom ExpansionTile widget from https://stackoverflow.com/a/58221096/19329222
It have errors (I think because the answer is 3 years old) but you can get rid of them by replacing the incorrect syntax with the correct one from the original ExpansionTile widget file.
This just work, but go through solution and adopt solution with your project design pattern and adopt it with your state management(Provider)
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new ExpansionTileSample());
}
class ExpansionTileSample extends StatefulWidget {
#override
ExpansionTileSampleState createState() => new ExpansionTileSampleState();
}
class ExpansionTileSampleState extends State {
String foos = 'One';
int _key;
_collapse() {
int newKey;
do {
_key = new Random().nextInt(10000);
} while(newKey == _key);
}
#override
void initState() {
super.initState();
_collapse();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('ExpansionTile'),
),
body: new ExpansionTile(
key: new Key(_key.toString()),
initiallyExpanded: false,
title: new Text(this.foos),
backgroundColor: Colors.gray,
children: [
new ListTile(
title: const Text('One'),
onTap: () {
setState(() {
this.foos = 'One';
_collapse();
});
},
),
new ListTile(
title: const Text('Two'),
onTap: () {
setState(() {
this.foos = 'Two';
_collapse();
});
},
),
new ListTile(
title: const Text('Three'),
onTap: () {
setState(() {
this.foos = 'Three';
_collapse();
});
},
),
]
),
),
);
}
}

Flutter navigation push, while keeping the same Appbar

I'm currently building a Flutter app where I'm struggling to figure out the best way to implement navigation.
I have 2 pages which are:
HomePage: from there I want to use an IndexedStack to manage the feed.
ProfilePage: the profile page, which (graphically) shares the same AppBar and the same Drawer as the home page.
In my App the user reaches the HomePage immediately after logging in. There is no navigation involved.
From there, I now have a TextButton, which calls Navigator.of(context).pushNamed(AppRoutes.profile).
As I said, both pages share the same Appbar and Drawer, so I created a custom myScaffold.
Both pages use this scaffold.
So the behavior is correct, since after clicking the button, the ProfilePage is moved over the HomePage.
My problem is that graphically the appbar should remain the same, but when the profile page is pushed, the animation makes it clear that it is not the same app bar.
Is it possible to animate the entry of the profile page, without
animating the rebuilding of the appbar?
Or is it possible to push a route directly into the scaffold content?
As an alternative I was just thinking of writing a function which
returns the page widget to be displayed within the scaffold content.
But this kind of approach doesn't seem right to me, since there are
routes.
From the official documentation you can see from the Interactive example what I mean:
Docs
When the second route is built over the first one, a new Appbar is built over the previous one.
But what if I need the appbar to stay the same?
You can create a sub-navigator using Navigator class.
I created a routes library (routes.dart) in my current project for navigating to other screens while bottomNavigationBar is still displayed. Using the same idea, you can perform navigations while using the same AppBar.
Here's the sample codes for your scenario.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorKey: Routes.rootNavigatorKey,
initialRoute: Routes.PAGE_INITIAL,
onGenerateRoute: Routes.onGenerateRoute,
);
}
}
routes.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter2sample/pages/home_page.dart';
import 'package:flutter2sample/pages/initial_page.dart';
import 'package:flutter2sample/pages/main_page.dart';
import 'package:flutter2sample/pages/profile_page.dart';
class Routes {
Routes._();
static const String PAGE_INITIAL = '/';
static const String PAGE_MAIN = '/main';
static const String PAGE_HOME = '/home';
static const String PAGE_PROFILE = '/profile';
static final GlobalKey<NavigatorState> rootNavigatorKey =
GlobalKey<NavigatorState>();
static final GlobalKey<NavigatorState> mainNavigatorKey =
GlobalKey<NavigatorState>();
static String currentSubNavigatorInitialRoute;
static CupertinoPageRoute<Widget> onGenerateRoute(RouteSettings settings) {
Widget page;
switch (settings.name) {
case PAGE_INITIAL:
page = InitialPage();
break;
case PAGE_MAIN:
page = MainPage();
break;
case PAGE_HOME:
page = HomePage();
break;
case PAGE_PROFILE:
page = ProfilePage();
break;
}
if (settings.name == PAGE_INITIAL &&
currentSubNavigatorInitialRoute != null) {
// When current sub-navigator initial route is set,
// do not display initial route because it is already displayed.
return null;
}
return CupertinoPageRoute<Widget>(
builder: (_) {
if (currentSubNavigatorInitialRoute == settings.name) {
return WillPopScope(
onWillPop: () async => false,
child: page,
);
}
return page;
},
settings: settings,
);
}
/// [MaterialApp] navigator key.
///
///
static NavigatorState get rootNavigator => rootNavigatorKey.currentState;
/// [PAGE_MAIN] navigator key.
///
///
static NavigatorState get mainNavigator => mainNavigatorKey.currentState;
/// Navigate to screen via [CupertinoPageRoute].
///
/// If [navigator] is not set, it will use the [rootNavigator].
static void push(Widget screen, {NavigatorState navigator}) {
final CupertinoPageRoute<Widget> route = CupertinoPageRoute<Widget>(
builder: (_) => screen,
);
if (navigator != null) {
navigator.push(route);
return;
}
rootNavigator.push(route);
}
/// Navigate to route name via [CupertinoPageRoute].
///
/// If [navigator] is not set, it will use the [rootNavigator].
static void pushNamed(
String routeName, {
NavigatorState navigator,
Object arguments,
}) {
if (navigator != null) {
navigator.pushNamed(routeName, arguments: arguments);
return;
}
rootNavigator.pushNamed(routeName, arguments: arguments);
}
/// Pop current route of [navigator].
///
/// If [navigator] is not set, it will use the [rootNavigator].
static void pop<T extends Object>({
NavigatorState navigator,
T result,
}) {
if (navigator != null) {
navigator.pop(result);
return;
}
rootNavigator.pop(result);
}
}
//--------------------------------------------------------------------------------
/// A navigator widget who is a child of [MaterialApp] navigator.
///
///
class SubNavigator extends StatelessWidget {
const SubNavigator({
#required this.navigatorKey,
#required this.initialRoute,
Key key,
}) : super(key: key);
final GlobalKey<NavigatorState> navigatorKey;
final String initialRoute;
#override
Widget build(BuildContext context) {
final _SubNavigatorObserver _navigatorObserver = _SubNavigatorObserver(
initialRoute,
navigatorKey,
);
Routes.currentSubNavigatorInitialRoute = initialRoute;
return WillPopScope(
onWillPop: () async {
if (_navigatorObserver.isInitialPage) {
Routes.currentSubNavigatorInitialRoute = null;
await SystemNavigator.pop();
return true;
}
final bool canPop = navigatorKey.currentState.canPop();
if (canPop) {
navigatorKey.currentState.pop();
}
return !canPop;
},
child: Navigator(
key: navigatorKey,
observers: <NavigatorObserver>[_navigatorObserver],
initialRoute: initialRoute,
onGenerateRoute: Routes.onGenerateRoute,
),
);
}
}
//--------------------------------------------------------------------------------
/// [NavigatorObserver] of [SubNavigator] widget.
///
///
class _SubNavigatorObserver extends NavigatorObserver {
_SubNavigatorObserver(this._initialRoute, this._navigatorKey);
final String _initialRoute;
final GlobalKey<NavigatorState> _navigatorKey;
final List<String> _routeNameStack = <String>[];
bool _isInitialPage = false;
/// Flag if current route is the initial page.
///
///
bool get isInitialPage => _isInitialPage;
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
_routeNameStack.add(route.settings.name);
_isInitialPage = _routeNameStack.last == _initialRoute;
}
#override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
_routeNameStack.remove(route.settings.name);
_isInitialPage = _routeNameStack.last == _initialRoute;
}
#override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
_routeNameStack.remove(route.settings.name);
_isInitialPage = _routeNameStack.last == _initialRoute;
}
#override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
_routeNameStack.remove(oldRoute.settings.name);
_routeNameStack.add(newRoute.settings.name);
_isInitialPage = _routeNameStack.last == _initialRoute;
}
}
initial_page.dart
import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';
class InitialPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Initial Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('This is INITIAL page'),
TextButton(
onPressed: () => Routes.pushNamed(Routes.PAGE_MAIN),
child: const Text('To Main page'),
),
],
),
),
);
}
}
main_page.dart
import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Main Page'),
),
body: SubNavigator(
navigatorKey: Routes.mainNavigatorKey,
initialRoute: Routes.PAGE_HOME,
),
);
}
}
home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow,
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('This is HOME page'),
TextButton(
onPressed: () => Routes.pushNamed(
Routes.PAGE_PROFILE,
navigator: Routes.mainNavigator,
),
child: const Text('To Profile page'),
),
],
),
),
),
);
}
}
profile_page.dart
import 'package:flutter/material.dart';
import 'package:flutter2sample/routes.dart';
class ProfilePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('This is PROFILE page'),
TextButton(
onPressed: () => Routes.pop(navigator: Routes.mainNavigator),
child: const Text('Back to Home page'),
),
],
),
),
),
);
}
}

[FLUTTER]: Programmatically change tabs in the CustomNavigator from SecondScreen to FirstScreen

I'm currently making an app with bottom navigator. And I have troubles with navigating from SecondScreen to the FirstScreen, programmatically, inside the SecondScreen file. But I have no idea how to do it. Because I can't have the access to the CustomNavigatorState part of the CustomNavigator class.
My main.dart file:
import 'package:flutter/material.dart';
import './screens/custom_navigator.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Name',
home: Scaffold(
body: CustomNavigator(),
),
);
}
}
My custom_navigator.dart file, which includes CustomNavigator class and _CustomNavigatorState class:
import 'package:flutter/material.dart';
import './first_second.dart';
import './second_screen.dart';
import './third_screen.dart';
import '../widgets/tab_navigator.dart';
class CustomNavigator extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CustomNavigatorState();
}
class _CustomNavigatorState extends State<CustomNavigator> {
String _currentScreen = FirstScreen.route;
List<String> _screenKeys = [
FirstScreen.route,
SecondScreen.route,
ThirdScreen.route,
];
Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
FirstScreen.route: GlobalKey<NavigatorState>(),
SecondScreen.route: GlobalKey<NavigatorState>(),
ThirdScreen.route: GlobalKey<NavigatorState>(),
};
int _selectedIndex = 0;
void changeTab(String tabItem, int index) {
_selectedTab(tabItem, index);
}
void _selectedTab(String tabItem, int index) {
if (tabItem == _currentScreen) {
_navigatorKeys[tabItem].currentState.popUntil((route) => route.isFirst);
} else {
setState(() {
_currentScreen = _screenKeys[index];
_selectedIndex = index;
});
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _navigatorKeys[_currentScreen].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentScreen != FirstScreen.route) {
_selectedTab(FirstScreen.route, 1);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
resizeToAvoidBottomPadding: true,
body: Stack(
children: [
_buildOffstageNavigator(FirstScreen.route),
_buildOffstageNavigator(ScreenScreen.route),
_buildOffstageNavigator(ThirdScreen.route),
],
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
_selectedTab(_screenKeys[index], index);
},
currentIndex: _selectedIndex,
items: [
BottomNavigationBarItem(
label: 'First',
),
BottomNavigationBarItem(
label: 'Second',
),
BottomNavigationBarItem(
label: 'Third',
),
],
),
),
);
}
Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentScreen != tabItem,
child: TabNavigator(
navigatorKey: _navigatorKeys[tabItem],
tabItem: tabItem,
),
);
}
}
TabNavigator class, where the screens added.
import 'package:flutter/material.dart';
import '../screens/first_screen.dart';
import '../screens/second_screen.dart';
import '../screens/third_screen.dart';
class TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final String tabItem;
const TabNavigator({
Key key,
this.navigatorKey,
this.tabItem,
}) : super(key: key);
#override
Widget build(BuildContext context) {
Widget child;
if (tabItem == FirstScreen.route) {
child = FirstScreen();
} else if (tabItem == SecondScreen.route) {
child = SecondScreen();
} else if (tabItem == ThirdScreen.route) {
child = ThirdScreen();
}
return Navigator(
key: navigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => child,
);
},
);
}
}
I tried to navigate with Navigator.push and Navigator.pushNamed, but it navigates inside SecondScreen without changing the BottomNavigationTabBars.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => SecondScreen(),
),
);
Navigator.of(context).pushNamed(SecondScreen.route);
Also I can't use Provider, because I don't have access to _CustomNavigatorState class. Could anybody offer me any decision of the problem. Thanks.
I notice you have the nested Scaffolds, it's probably better to move your BottomNavigationBar to the outer Scaffold so you only have one Scaffold in your app. For the body of the outter Scaffold you will have your Stack
Regarding the navigator issues. The body of your app in a Stack with three offstage widgets. Only one of the widgets is visible at a time. When changing between each Offstage widget you don't actually navigate to it, all you have to do is change your _currentScreen to which ever you would like. So if you're on page one and would like to "push" to page 2 then have something like
onPressed: () {
SetState(() {
_currentScreen = FirstScreen.route;
}
}
Then when your body rebuilds from the setState it will set the FirstScreen to be onstage and all other screens to be offstage. Showing you the FirstScreen.

How to declare observable in Mobx flutter to trigger when a field of a class is changed?

I am learning Mobx Flutter and would like to have an observer showing modification of a field in a class.
When using an int instead of a custom class it is working.
So i suspect I am not declaring properly the class in the store
Here is the code of my store
import 'package:mobx/mobx.dart';
// generated file
part 'bikeModel.g.dart';
class Cell {
String description;
String value;
String unit;
Cell({this.description, this.value, this.unit});
}
class BikeData = _BikeData with _$BikeData;
abstract class _BikeData with Store {
Timer _timerSimu;
#observable
int cadence = 0;
#observable
Cell cello = Cell(description: 'desc', value: 'oo', unit: 'km/h');
#action
startSimul() {
int _tick = 0;
cadence++;
cello.value = cadence.toString();
_timerSimu = Timer.periodic(Duration(seconds: 1), (timer) {
print('Screen simu is ticking...$_tick');
_tick++;
cadence++;
});
}
#action
stopSimu() {
_timerSimu.cancel();
}
}
and here is the main code
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx_first/bikeModel.dart';
import 'globals.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MobX',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage();
#override
Widget build(BuildContext context) {
BikeData store = BikeData();
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0) {
// Right swipe
print('this is a right swipe');
} else {
// left swipe
print('this is a left swipe');
}
},
child: Scaffold(
appBar: AppBar(
title: Text('MobX Test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Observer(
builder: (_) => Text('cello.value ${store.cello.value}')),
Observer(builder: (_) => Text('cadence ${store.cadence}')),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: store.startSimul,
tooltip: 'Change',
child: Icon(Icons.add),
),
),
);
}
}
cadence is changing everything second on the screen but not cello.value
What is the proper way to declare cello observable?
Problem is that you are just changing value of object item(value). You have to completely change object, then only mobx find value is change.
Replace your following line
cello.value = cadence.toString();
With following code:
cello = Cell(
description: cello.description,
value: cadence.toString(),
unit: cello.unit);

Flutter stateful widget is not updating while calling from Navigation drawer

I am trying to update my stateful widget of my class while calling it from Navigation Drawer. stateless widget are being updated when they are called from Navigation Drawer. Here is my Navigation drawer from where I am calling 'Fragment First'.
class DrawerItem {
String title;
IconData icon;
DrawerItem(this.title, this.icon);
}
class HomePage extends StatefulWidget {
final drawerItems = [
new DrawerItem("First Fragment", Icons.rss_feed),
new DrawerItem("Second Fragment", Icons.local_pizza),
new DrawerItem("Third Fragment", Icons.info)
];
#override
State<StatefulWidget> createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
int _selectedDrawerIndex = 0;
_getDrawerItemWidget(int pos) {
switch (pos) {
case 0:
return new FirstFragmen(pos);
case 1:
return new FirstFragmen(pos);
case 2:
return new FirstFragmen(pos);
default:
return new Text("Error");
}
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop(); // close the drawer
}
#override
Widget build(BuildContext context) {
List<Widget> drawerOptions = [];
for (var i = 0; i < widget.drawerItems.length; i++) {
var d = widget.drawerItems[i];
drawerOptions.add(
new ListTile(
leading: new Icon(d.icon),
title: new Text(d.title),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
)
);
}
return new Scaffold(
appBar: new AppBar(
// here we display the title corresponding to the fragment
// you can instead choose to have a static title
title: new Text(widget.drawerItems[_selectedDrawerIndex].title),
),
drawer: new Drawer(
child: new Column(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: new Text("John Doe"), accountEmail: null),
new Column(children: drawerOptions)
],
),
),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
}
Here is Fragment First:
class FirstFragment extends StatefulWidget {
int pos;
FirstFragment(this.pos);
#override
_FirstFragmentState createState() => new _FirstFragmentState(pos);
}
class _FirstFragmentState extends State<FirstFragment> {
int pos;
_FirstFragmentState(this.pos);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Center(
child: new Text("Hello Fragment $pos"), >printing 'pos' only. It remains
> same all time when new class is called.
);
}
}
if I am using stateless widget then its being updated, but stateful widget is not being updated. I've tried to debug using breakpoints but _FirstFragmentState class is called only once. Is there any way to redraw all widgets when its called second time.
The state is created once and then shared for multiple instances of your widget. Since you're taking pos in the state constructor, it's not being updated later when widgets change.
One way to solve this would be to remove the pos in your _FirstFragmentState, and reference the pos in FirstFragment directly. You can access it through the widget field of your state class.
class _FirstFragmentState extends State<FirstFragment> {
#override
Widget build(BuildContext context) {
return new Center(
child: new Text("Hello Fragment ${widget.pos}"), // -> use pos from FirstFragment
);
}
}