Flutter : sending data across multiple screens - flutter

I have 3 Widget MyApp Widget ,Home Widget, and Sliver Appbar Widget, It's connected to each other. Example MyApp Widget -> Home Widget -> SliverAppbar Widget.
My question is , how to Passing data from My App Widget directly to SliverAppBar Widget ?
I found what i think it's can solve my case that is Inherited Widget. But i confused to understading to use this widget.
I already try using Inherited Widget as documentation like this :
MyApp Widget
class SettingsApp extends InheritedWidget {
SettingsApp({Key key, this.isDarkMode = false, Widget child})
: super(key: key, child: child);
final bool isDarkMode;
static SettingsApp of(BuildContext context) {
return (context.dependOnInheritedWidgetOfExactType<SettingsApp>());
}
#override
bool updateShouldNotify(SettingsApp oldWidget) {
return true;
}
}
SliverAppBar Widget
class SliverAppBarCustom extends StatelessWidget {
final Box detbBox = Hive.box("debt_box");
final UserModelHive userModelHive = Hive.box("user_box").get("userSession");
#override
Widget build(BuildContext context) {
final isDarkMode =
context.dependOnInheritedWidgetOfExactType<SettingsApp>().isDarkMode;
print(isDarkMode.toString());
var mediaQuery = MediaQuery.of(context);
var textTheme = Theme.of(context).textTheme;
return Text(isDarkMode.toString());
}
}
But i get this error :
Log
The following NoSuchMethodError was thrown building SliverAppBarCustom(dirty):
The getter 'isDarkMode' was called on null.
Receiver: null
Tried calling: isDarkMode

Using ScopedModel
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() => runApp(MyApp());
class SettingsModel extends Model {
bool _isDarkMode;
SettingsModel({bool isDarkMode}) : _isDarkMode = isDarkMode ?? false;
bool get isDarkModel => _isDarkMode;
set isDarkModel(bool value) {
_isDarkMode = value;
notifyListeners();
}
void switchTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
static SettingsModel of(BuildContext context) {
return ScopedModel.of<SettingsModel>(context);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<SettingsModel>(
model: SettingsModel(isDarkMode: true),
child: MaterialApp(
home: InitPage(),
),
);
}
}
class InitPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Init Page")),
body: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ScopedModelDescendant<SettingsModel>(
builder: (context, child, model) {
return Text('Is Dark Mode: ${model.isDarkModel}');
},
),
RaisedButton(
child: Text("Next Page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
},
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Page"),
),
body: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ScopedModelDescendant<SettingsModel>(
builder: (context, child, model) {
return Text('Is Dark Mode: ${model.isDarkModel}');
},
),
RaisedButton(
child: Text("Switch Theme"),
onPressed: SettingsModel.of(context).switchTheme,
),
],
),
),
);
}
}
Important: You should not change _isDarkModel without notifyListeners(). If you do UI may not update.

Related

How to use ViewModelWidget in the onPressed callback of a button in Flutter?

I am trying to use the ViewModelWidget class from the stacked package. I am able to show the TextWidget which extends ViewModelWidget in the body of the page, but cannot show it in the bottom sheet because I am showing the bottom sheet from the onPressed function and that function does not have access to the view model.
Here is the code:
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyView(),
);
}
}
class MyViewModel extends BaseViewModel {
String title = 'Hello';
void updateTitle() {
title = 'Bye';
notifyListeners();
}
}
class MyView extends StatelessWidget {
const MyView({super.key});
#override
Widget build(BuildContext context) {
return ViewModelBuilder<MyViewModel>.reactive(
viewModelBuilder: () => MyViewModel(),
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: const Text('My View'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const TextWidget(),
ElevatedButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return const SizedBox(child: Center(child: TextWidget(),), height: 200,);
},
);
},
child: const Text('Show Modal'),
),
],
),
),
);
}
}
class TextWidget extends ViewModelWidget<MyViewModel> {
const TextWidget({super.key});
#override
Widget build(BuildContext context, MyViewModel viewModel) {
return Text(viewModel.title);
}
}
And here's the error that happens when I try to open the bottom sheet:
How can I solve this error?
I think i got it
The .nonReactive constructor is best used for providing your ViewModel
to multiple child widgets that will make use of it. It was created to
make it easier to build and provide the same ViewModel to multiple
UI's. It was born out of the Responsive UI architecture where we would
have to provide the same ViewModel to all the different responsive
layouts.
As a result you should give a try to return ViewModelBuilder<HomeViewModel>.nonReactive(
Check the doc #Non Reactive : https://pub.dev/packages/stacked

Pass data to multiple screens with inheritedwidget

There are some confusions about InheritedWidget that I don't understand.
I have searched and read some QAs about InheritedWidget on stackoverflow, but there are still things that I don't understand.
First of all, let's create a scenario.
This is my InheritedWidget:
class MyInheritedWidget extends InheritedWidget {
final String name;
MyInheritedWidget({
#required this.name,
#required Widget child,
Key key,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) =>
oldWidget.name != this.name;
static MyInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
and this is MyHomePage that contains the MyInheritedWidget. MyInheritedWidget has two children: WidgetA and a button that navigates to another screen, in this case Page1.
class MyHomePage extends StatefulWidget {
#override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: MyInheritedWidget(
name: 'Name',
child: Column(
children: [
WidgetA(),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page1(),
),
);
},
child: Text('Go to page 1'),
)
],
),
),
),
);
}
}
Inside WidgetA there is a text widget that displays the name field from MyInheritedWidget and another button that navigates to Page2.
class WidgetA extends StatefulWidget {
#override
_WidgetAState createState() => _WidgetAState();
}
class _WidgetAState extends State<WidgetA> {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Column(
children: [
Text(myInheritedWidget.name),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page2(),
),
);
},
child: Text('Go to page 2'),
)
],
);
}
}
Page1 and Page2 each has only a text widget that displays the name field from MyInheritedWidget.
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Page 1'),
),
body: Text(myInheritedWidget.name),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
),
body: Text(myInheritedWidget.name),
);
}
}
In this scenario, the name field of MyInheritedWidget is not accessible form Page1 and Page2, but it can be accessed in WidgetA.
Now lets get to the question:
It is said that an InheritedWidget can be accessed from all of its descendants. What does descendant mean?
In MyHomePage, I know WidgetA is a descendant of MyInheritedWidget. but, is Page1 also a descendant of MyInheritedWidget?
If the answer is no, How can I make Page1 a descendant of MyInheritedWidget?
Do I need to wrap it again inside MyInheritedWidget?
What if there is a chain of navigations like this: Page1-> Page2 -> Page3 ... Page10 and I want to access MyInheritedWidget in Page10, Do I have to wrap each of the pages inside MyInheritedWidget?
As #pskink says, MyHomePage pushes Page1, which is a descendant of Navigator, which is under MaterialApp, not MyInheritedWidget. The easiest solution is to create MyInheritedWidget above MaterialApp. This is my code (using ChangeNotifierProvider instead of MyInheritedWidget).
void main() {
setupLocator();
runApp(DevConnectorApp());
}
class DevConnectorApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final log = getLogger('DevConnectorApp');
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthService>(
create: (ctx) => AuthService(),
),
ChangeNotifierProxyProvider<AuthService, ProfileService>(
create: (ctx) => ProfileService(),
update: (ctx, authService, profileService) =>
profileService..updateAuth(authService),
),
],
child: Consumer<AuthService>(builder: (ctx, authService, _) {
log.v('building MaterialApp with isAuth=${authService.isAuth}');
return MaterialApp(
Here is an example using multiple Navigators to scope the InheritedWidget. The widget ContextWidget creates an InheritedWidget and a Navigator and has child widgets for the screens in your example.
class InheritedWidgetTest extends StatefulWidget {
#override
State createState() => new InheritedWidgetTestState();
}
class InheritedWidgetTestState extends State<InheritedWidgetTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
children: [
ContextWidget('First'),
ContextWidget('Second'),
],
),
),
);
}
}
class ContextWidget extends StatelessWidget {
Navigator _getNavigator(BuildContext context, Widget child) {
return new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(builder: (context) {
return child;
});
},
);
}
final name;
ContextWidget(this.name);
#override
Widget build(BuildContext context) {
return MyInheritedWidget(
name: this.name,
child: Expanded(
child: _getNavigator(
context,
PageWidget(),
),
),
);
}
}
class PageWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
WidgetA(),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Page1(),
),
);
},
child: Text('Go to page 1'),
)
],
);
}
}

I am using notifyListner from flutter Provider package but my UI is not updating

I am using notifyListner from flutter Provider package but my UI is not updating whenever I am typing the letters in the TextField. I am making this app just to understand how Provider works. My appbar and text is supposed to change whenever I type the text in TextFiled. Here's my code,
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: MyAppBar(),
),
body: SafeArea(
child: Column(
children: [
MyTextField(),
Expanded(
child: MyTextWidget(),
),
],
),
),
),
),
);
}
}
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
onChanged: (newValue) {
Provider.of<Data>(context, listen: true).changeString(newValue);
},
),
);
}
}
class MyTextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text(Provider.of<Data>(context, listen: true).appName),
);
}
}
class MyAppBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(Provider.of<Data>(context, listen: true).appName);
}
}
class Data extends ChangeNotifier {
String appName = 'Understanding Provider';
void changeString(String newString) {
appName = newString;
notifyListeners();
}
}
Please somebody help, Thanks!
You should not listen to your provider when you update it inside MyTextField:
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
onChanged: (newValue) {
Provider.of<Data>(context, listen: false).changeString(newValue);
},
),
);
}
}
Set listen: false.

Can't add or update a list

So I'm trying to make a list that contains some widgets and then add a new widget to it when I press a button, but it doesn't seem to be working
This is the code:
class MessagesProvider extends ChangeNotifier{
List<dynamic> mesgs = [
new chatBubbleSend(),
new chatBubbleReceiver(),
new chatBubbleReceiver()
];
bool loading = true;
addMesg(){
mesgs.add(chatBubbleSend());
print(mesgs.length);
print(mesgs);
notifyListeners();
}
printMesg(){
print(mesgs.length);
print(mesgs);
}
removeMesg(){
mesgs.removeLast();
print(mesgs.length);
print(mesgs);
notifyListeners();
}
}
and this is what i get when i press the add, remove or print buttons
add,remove,print
and this is the list builder code
ChangeNotifierProvider<MessagesProvider>(
create: (context) => MessagesProvider(),
child: ChatMessages()
),
class ChatMessages extends StatelessWidget {
#override
Widget build(BuildContext context) {
final mesgs = Provider.of<MessagesProvider>(context, listen: false).mesgs;
return ListView.builder(
shrinkWrap: true,
itemCount: mesgs.length,
itemBuilder: (context,index)=> mesgs[index],
);
}
}
I have looking for a solution for over 8 hours now, and still, I couldn't fix it.
I jumped the gun with my first answer sorry.
When trying to recreate I ran into the same frustrating issue - focusing on the the provider being the problem until I realised it's actually the rendering of the updated list that's the issue.
You need to use a list builder to render the updating list in a change notifier consumer in a stateful widget
Full working example below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class WidgetListProvider with ChangeNotifier {
List<Widget> widgets = [];
int listLength = 0;
void addWidget(){
Widget _widget = Text('Hello');
widgets.add(_widget);
listLength = widgets.length;
print('Added a widget');
notifyListeners();
}
void removeWidget(){
if (widgets.length > 0) {
widgets.removeLast();
listLength = widgets.length;
print('Removed a widget');
notifyListeners();
}
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget _appBar (BuildContext context) {
return AppBar(
title: Text('My App'),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(context),
// You need to define widgets that update when a provider changes
// as children of a consumer of that provider
body: Consumer<WidgetListProvider>(builder: (context, widgetProvider, child){
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
child: Text('Add widget'),
onPressed: () {
widgetProvider.addWidget();
},
),
RaisedButton(
child: Text('Remove Widget'),
onPressed: () {
widgetProvider.removeWidget();
},
),
Row(
children: [
Text('Number of Widgets: '),
Text(widgetProvider.listLength.toString()),
],
),
Container(
height: MediaQuery.of(context).size.height*0.6,
child: ListView.builder(itemCount: widgetProvider.widgets.length, itemBuilder: (BuildContext context, int index){
return widgetProvider.widgets[index];
})
)
],
),
);
}
),
);
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => WidgetListProvider(),
child: MyApp(),
)
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: HomePage(),
);
}
}

How to reuse the same layout screen without creating a new widget tree branch

I am developing Flutter Web Application.
The object is to reuse the same layout screen widget(Drawer, AppBar) for most of route screen.
I have tried create a new Scaffold class and add body widget to each screen.
The problem is every time I navigate to a new screen. There is a new (MyScaffold) created on the widget tree. So it is not good for performance.
I also tried to use nested router, the problem is nested router is not supported by url that I can not navigate to the screen by typing the URL.
Is there any other proper way to deal with this problem.
Thanks
Add the code example :
import 'package:flutter/material.dart';
void main() => runApp(AppWidget());
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('Go back!'),
),
),
);
}
}
And I will try to explain the question better.
As you can see First Screen and Second Screen has Exactly same structure of widget tree. But every time flutter is remove the Screen Widget and create a new one.
I also tried to change the code to create a new MyScaffold and reuse the same Widget class :
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => MyScallfold(
bodyWidget: FirstScreen(),
),
'/second': (context) => MyScallfold(
bodyWidget: SecondScreen(),
),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
child: Text('To Screen 2!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('To Screen 1!'),
);
}
}
class MyScallfold extends StatelessWidget {
Widget bodyWidget;
MyScallfold({this.bodyWidget});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: bodyWidget,
);
}
}
Bus I noticed every time I use the navigation, all the widget of the tree is rebuilt (The renderObject #id is changed)
So is it possible to reuse the same RenderObject (AppBar, RichText) in flutter to optimise the performance ?
The quick answer is no, not yet anyway. Currently when you use Navigator it refreshes the page and rebuilds the full view.
The most efficient way on Flutter web currently would be to use a TabController with a TabBarView in a Stateful widget with SingleTickerProviderStateMixin.
It only loads what Widget is on screen, but doesn't require the page to reload to view other pages. Your example would look like this (I have added animation to transition to the next page, but you can remove it):
import 'package:flutter/material.dart';
TabController tabController;
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateMixin {
int activeTab = 0;
#override
void initState() {
tabController = TabController(length: 3, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
activeTab = tabController.index;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: Expanded(
child: TabBarView(
controller: tabController,
children: <Widget>[
FirstScreen(), //Index 0
SecondScreen(), //Index 1
ThirdScreen(), //Index 2
],
),
),
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(2);
},
child: Text('To Screen 3!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(0);
},
child: Text('To Screen 1!'),
);
}
}
class ThirdScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(1);
},
child: Text('To Screen 2!'),
);
}
}