Pass parameters to a route widget correctly - flutter

I am new to flutter, and have some difficulties understanding how to correctly pass parameters to a widget that is navigated to.
My goal is, that when the users clicks on a button, I want to start up a wizard controller with a certain enum parameter based on what button the user clicked.
The wizard controller has an app bar but the primary content is a dynamic child wizard flow widget which is chosen based on the enum parameter. The wizard controller (and its children) needs to be stateful because it, among other things, holds information about the current page in the chosen wizard flow and a model which holds data for the whole wizard flow.
As far as I can see there are two options of instantiating the wizard controller with the enum parameter:
Option 1.
//Pass the parameters when the route is pushed
onPressed: () {
Navigator.pushNamed(context, '/wizard', arguments: EFlowType.WizardFlow2);
},
//In the build method, extract the parameter from the navigator, and use it here:
class WizardController extends StatefulWidget {
WizardController({Key? key}) : super(key: key);
#override
State<WizardController> createState() => _WizardControllerState();
}
class _WizardControllerState extends State<WizardController> {
StatefulWidget? dynamicWidget;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final flowType = ModalRoute.of(context)!.settings.arguments as EFlowType;
switch (flowType) {
case EFlowType.WizardFlow1:
//Prepare models, set dynamicWidget and do alot of work
break;
case EFlowType.WizardFlow2:
//Prepare models, set dynamicWidget and do alot of work
break;
}
return Scaffold(
appBar: AppBar(
title: Text('Wizard controller'),
),
body: dynamicWidget
);
}
....
The problem is here that I would like to access the flowType parameter in the initState (or in the WizardController class constructor), so I dont need to do all the initialization work every time the widget is being rebuild/UI is updated.
If I try to access the flowtype in the initstate I get this error which I cannot come around: FlutterError (dependOnInheritedWidgetOfExactType<_ModalScopeStatus>() or dependOnInheritedElement() was called before _WizardControllerState.initState() completed.
It is not possible to access the context in the WizardController class, so that is not an option.
Option 2.
In the MaterialApp method I can declare an extra onGenerateRoute setting, for these routes in my app that has parameters:
class WizardController extends StatefulWidget {
EFlowType flowType = EFlowType.MeterChange;
WizardController({Key? key, required this.flowType}) : super(key: key);
#override
State<WizardController> createState() => _WizardControllerState();
}
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/wizard') {
return MaterialPageRoute(builder: (_) => WizardController(flowType: settings.arguments as EFlowType));
}
This makes the flowType available in the initState. But why on earth would I declare context and logic-specific stuff where I am defining my routes? Is this a preferred way of defining widget parameters?
So how would you normally go around this quite normal problem?

Related

StatelessWidget gets rebuilt even though parameters stay the same; notifyListeners() with ChangeNotifier

I've got two StatelessWidgets, one is a child of another. There is also a progress update function which updates the state in the external TransferState.
Once updateProgress function is being called the TransferIndicator widget gets rebuilt immediately. On the other hand, its parent (TransfersListTile) build method isn't called.
It works as expected, however I can't really work out what's the mechanism that's being used here. How Flutter decides to rebuild the: _TransferIndicator given that the parameter is a string hash that's not being changed, but only used as a lookup ID to reach the map in TransferState and load the status and progress.
Documentation: https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html says:
"The build method of a stateless widget is typically only called in
three situations: the first time the widget is inserted in the tree,
when the widget's parent changes its configuration, and when an
InheritedWidget it depends on changes."
If: notifyListeners(); function is removed, the widget doesn't get rebuilt.
It seem to be closely related to: ChangeNotifier, but couldn't find the exact info how it works.
In doc here: https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#changenotifier there is an example involving ChangeNotifier, however doesn't the receiving widget need to be wrapped around: Consumer (https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#consumer)?
In my case there is no Consumer wrapping.
class TransfersListTile extends StatelessWidget {
TransfersListTile(this.transfer, {Key? key}) : super(key: key);
final Transfer transfer;
#override
Widget build(BuildContext context) {
return ListTile(
leading: _TransferIndicator(transfer.hash),
title: Text(transfer.name!),
);
}
}
class _TransferIndicator extends StatelessWidget {
const _TransferIndicator(this.hash, {Key? key}) : super(key: key);
final String? hash;
#override
Widget build(BuildContext context) {
final status = context.select((TransferState s) => s.map[hash]?.status) ?? TransferStatus.pending;
final progress = context.select((TransferState s) => s.map[hash].progress.percentage);
return CircularProgressIndicator(
value: status == TransferStatus.completed ? 100 : (progress / 100),
);
}
}
function:
class TransferState with ChangeNotifier {
updateProgress(String hash, TransferProgress progress) {
map[hash]?.progress = progress;
notifyListeners();
}
}
and provider part:
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => TransferState(),
],
child: MyApp(),
)
);
More info about the select method and other convenience methods can be found on the provider (https://pub.dev/packages/provider) package site.
Excerpt:
The easiest way to read a value is by using the extension methods on
[BuildContext]:
context.watch(), which makes the widget listen to changes on T
context.read(), which returns T without listening to it
context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.

How to access a variable data from the state of a flutter stateful widget from another stateful widget class's state

Hi guys I am facing a problem which I have tried to solve through multiple approaches but failed. I have a stateful widget class which has a variable in its state I need to access the data in this variable from another stateful widget's class's state, I have tried accessing the variable by creating a GlobalKey for the class which contains the variable, but when I access the variable I get null. Am I accessing the variable the wrong, way or is there a more appropriate way of doing it?
what i usually do in cases like this is i will define varableX globally so that i can access it from anywhere in my app. To do this install flutter_riverpod and define variableX like this:
final variableXProvider = StateProvider<bool>((ref)=> false/true);
inside your build method, you can access it like this
class StatefulWidget1 extends ConsumerStatefulWidget {
const StatefulWidget1({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _StatefulWidget1State();
}
class _StatefulWidget1State extends ConsumerState<StatefulWidget1> {
#override
Widget build(BuildContext context) {
//* watch variableX like this
final varibleX = ref.watch(variableXProvider.state).state;
return Scaffold(body: Column(
children: [
Text(varibleX),
//* to change the value of variableX you can do this
ElevatedButton(onPressed: (){
ref.read(variableXProvider.notifier).state = true/false;
}, child: Text('Change value'))
],),);
}
}
once you hit the button, the value changes without needing to call setState(). the ref object will automatically rebuild itself once it detects any changes.

Flutter : how to fetch data and manage app state

I'm trying Flutter and I need (I think I do) an app state management to share datas across widgets and dont have to make an http request each time a route is called.
I have Places and Events, so I first load my Places to list them at creation of app state with :
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => AppStateModel()..fetchPlaces(),
...
)
);
}
When I click on a Place, I go on place/id screen and as Places doesnt have events props yet, I'm trying to load them with :
class PlacePageArguments {
final String id;
PlacePageArguments(this.id);
}
class PlacePage extends StatefulWidget {
const PlacePage({Key? key}) : super(key: key);
#override
State<PlacePage> createState() => _PlacePageState();
}
class _PlacePageState extends State<PlacePage> {
String id = '';
#override
Widget build(BuildContext context) {
final args =
ModalRoute.of(context)!.settings.arguments as PlacePageArguments;
return Consumer<AppStateModel>(builder: (context, appState, child) {
id = args.id;
appState.fetchEvents(id);
final place = appState.getPlaceById(id);
return Scaffold(...);
})
}
}
But for sure, as I notifyChange to update widget, It does an infinite loop on fetch events.
What should I do ?
What is the best to achieve something like that, maybe a simple futurBuilder will work, but I want to add events and stay on the same page (add event with modal) and want instant result.
thanks for all
You have two options for bringing in the data for this and none of them require ChangeNotifierProvider.
You can pass data via constructors. This works fine for small widget trees but it can easily get complicated.
You can use Providers. Providers allow you to manage data and functions in one class that stays in one file. For more deals please look here.

How do i access a varible from another class in Flutter

I'm making my first flutter app.
it asks for some information then when you click a button it shows the information you entered on another page, I wanted to ask. How do I get a variable from another class?
so I can use the information entered on another page
It seems like you want to pass some data while navigating to another page. If I'm not wrong You should define a variable in the destination like this:
class NewPage extends StatefulWidget {
final int someInt;
NewPage({Key key, this.someInt}) : super(key: key);
#override
_NewPageState createState() => _NewPageState();
}
class _NewPageState extends State<NewPage> {
#override
Widget build(BuildContext context) {
return Container(
child: Container(child: Text("${widget.someInt}"),),
);
}
}
In the above code I passed someInt to NewPage class.
In the first page you should navigate like this:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewPage(someInt: 293,),
));
put above code in the onPressed and pass the data with constructor.

Controlling State from outside of a StatefulWidget

I'm trying to understand the best practice for controlling a StatefulWidget's state outside of that Widgets State.
I have the following interface defined.
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
I would like to create a StatefulWidget StartupPage that implements this interface. I expect the Widget to do the following:
When a button is pressed it would send an event over the onAppSelected stream. A controller would listen to this event and perform some action ( DB call, service request, etc ).
The controller can call showActivity or set message to have the view show progress with a message.
Because a Stateful Widget does not expose its State as a property, I don't know the best approach for accessing and modifying the State's attributes.
The way I would expect to use this would be something like this:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
I've thought about instantiating the Widget by passing in the state I want it to return in createState() but that feels wrong.
Some background on why we have this approach: We currently have a Dart web application. For view-controller separation, testability, and forward-thinking towards Flutter, we decided that we would create an interface for every view in our application. This would allow a WebComponent or a Flutter Widget to implement this interface and leave all of the controller logic the same.
There are multiple ways to interact with other stateful widgets.
1. findAncestorStateOfType
The first and most straightforward is through context.findAncestorStateOfType method.
Usually wrapped in a static method of the Stateful subclass like this :
class MyState extends StatefulWidget {
static of(BuildContext context, {bool root = false}) => root
? context.findRootAncestorStateOfType<_MyStateState>()
: context.findAncestorStateOfType<_MyStateState>();
#override
_MyStateState createState() => _MyStateState();
}
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return Container();
}
}
This is how Navigator works for example.
Pro:
Easiest solution
Con:
Tempted to access State properties or manually call setState
Requires to expose State subclass
Don't use this method when you want to access a variable. As your widget may not reload when that variable change.
2. Listenable, Stream and/or InheritedWidget
Sometimes instead of a method, you may want to access some properties. The thing is, you most likely want your widgets to update whenever that value changes over time.
In this situation, dart offer Stream and Sink. And flutter adds on the top of it InheritedWidget and Listenable such as ValueNotifier. They all do relatively the same thing: subscribing to a value change event when coupled with a StreamBuilder/context.dependOnInheritedWidgetOfExactType/AnimatedBuilder.
This is the go-to solution when you want your State to expose some properties. I won't cover all the possibilities but here's a small example using InheritedWidget :
First, we have an InheritedWidget that expose a count :
class Count extends InheritedWidget {
static of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<Count>();
final int count;
Count({Key key, #required Widget child, #required this.count})
: assert(count != null),
super(key: key, child: child);
#override
bool updateShouldNotify(Count oldWidget) {
return this.count != oldWidget.count;
}
}
Then we have our State that instantiate this InheritedWidget
class _MyStateState extends State<MyState> {
int count = 0;
#override
Widget build(BuildContext context) {
return Count(
count: count,
child: Scaffold(
body: CountBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
),
),
);
}
}
Finally, we have our CountBody that fetch this exposed count
class CountBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(Count.of(context).count.toString()),
);
}
}
Pros:
More performant than findAncestorStateOfType
Stream alternative is dart only (works with web) and is strongly integrated in the language (keywords such as await for or async*)
Automic reload of the children when the value change
Cons:
More boilerplate
Stream can be complicated
3. Notifications
Instead of directly calling methods on State, you can send a Notification from your widget. And make State subscribe to these notifications.
An example of Notification would be :
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
To dispatch the notification simply call dispatch(context) on your notification instance and it will bubble up.
MyNotification(title: "Foo")..dispatch(context)
Note: you need put above line of code inside a class, otherwise no context, can NOT call notification.
Any given widget can listen to notifications dispatched by their children using NotificationListener<T> :
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onTitlePush,
child: Container(),
);
}
bool onTitlePush(MyNotification notification) {
print("New item ${notification.title}");
// true meaning processed, no following notification bubbling.
return true;
}
}
An example would be Scrollable, which can dispatch ScrollNotification including start/end/overscroll. Then used by Scrollbar to know scroll information without having access to ScrollController
Pros:
Cool reactive API. We don't directly do stuff on State. It's State that subscribes to events triggered by its children
More than one widget can subscribe to that same notification
Prevents children from accessing unwanted State properties
Cons:
May not fit your use-case
Requires more boilerplate
You can expose the state's widget with a static method, a few of the flutter examples do it this way and I've started using it as well:
class StartupPage extends StatefulWidget {
static StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<StartupPageState>());
#override
StartupPageState createState() => new StartupPageState();
}
class StartupPageState extends State<StartupPage> {
...
}
You can then access the state by calling StartupPage.of(context).doSomething();.
The caveat here is that you need to have a BuildContext with that page somewhere in its tree.
There is another common used approach to have access to State's properties/methods:
class StartupPage extends StatefulWidget {
StartupPage({Key key}) : super(key: key);
#override
StartupPageState createState() => StartupPageState();
}
// Make class public!
class StartupPageState extends State<StartupPage> {
int someStateProperty;
void someStateMethod() {}
}
// Somewhere where inside class where `StartupPage` will be used
final startupPageKey = GlobalKey<StartupPageState>();
// Somewhere where the `StartupPage` will be opened
final startupPage = StartupPage(key: startupPageKey);
Navigator.push(context, MaterialPageRoute(builder: (_) => startupPage);
// Somewhere where you need have access to state
startupPageKey.currentState.someStateProperty = 1;
startupPageKey.currentState.someStateMethod();
I do:
class StartupPage extends StatefulWidget {
StartupPageState state;
#override
StartupPageState createState() {
this.state = new StartupPageState();
return this.state;
}
}
class DetectedAnimationState extends State<DetectedAnimation> {
And outside just startupPage.state
While trying to solve a similar problem, I discovered that ancestorStateOfType() and TypeMatcher have been deprecated. Instead, one has to use findAncestorStateOfType(). However as per the documentation, "calling this method is relatively expensive". The documentation for the findAncestorStateOfType() method can be found here.
In any case, to use findAncestorStateOfType(), the following can be implemented (this is a modification of the correct answer using the findAncestorStateOfType() method):
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.findAncestorStateOfType<_StartupPageState>();
#override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
The state can be accessed in the same way as described in the correct answer (using StartupPage.of(context).yourFunction()). I wanted to update the post with the new method.
You can use eventify
This library provide mechanism to register for event notifications with emitter
or publisher and get notified in the event of an event.
You can do something like:
// Import the library
import 'package:eventify/eventify.dart';
final EventEmitter emitter = new EventEmitter();
var controlNumber = 50;
List<Widget> buttonsGenerator() {
final List<Widget> buttons = new List<Widget>();
for (var i = 0; i < controlNumber; i++) {
widgets.add(new MaterialButton(
// Generate 10 Buttons afterwards
onPressed: () {
controlNumber = 10;
emitter.emit("updateButtonsList", null, "");
},
);
}
}
class AState extends State<ofYourWidget> {
#override
Widget build(BuildContext context) {
List<Widget> buttons_list = buttonsGenerator();
emitter.on('updateButtonsList', null, (event, event_context) {
setState(() {
buttons_list = buttonsGenerator();
});
});
}
...
}
I can't think of anything which can't be achieved by event driven programming. You are limitless!
"Freedom cannot be bestowed — it must be achieved."
- Elbert Hubbard
Have you considered lifting the state to the parent widget? It is a common, though less ideal than Redux, way to manage state in React as far as I know, and this repository shows how to apply the concept to a Flutter app.