Best way to manage DataTableSource for PaginatedDataTable? - dart

Writing my first Flutter application and we need to use a PaginatedDataTable. The docs say the source field should
generally have a lifetime longer than the PaginatedDataTable
widget itself; it should be reused each time the PaginatedDataTable
constructor is called.
https://docs.flutter.io/flutter/material/PaginatedDataTable/source.html
What is the best way to manage this? Is there a common pattern? My initial thought is the singleton pattern but I come from the Java world so I'm not sure if this is correct.
Can you also explain why the DataTableSource should be reused? Thanks.

DataTableSource is the state of your Table. It contains all your table data and whether or not rows are selected.
It must be persisted somewhere because you'd loose all your selection and potential loaded data if you recreated your DataSource anew everytime.
This is especially true considering data is lazy loaded, and potentially comes from http call.
Ideally you'll want to store your DataSource inside a StatefulWidget or something similar (InheritedWidget, a Stream, whatever).
class MyTable extends StatefulWidget {
#override
_MyTableState createState() => new _MyTableState();
}
class _MyTableState extends State<MyTable> {
final myDataSource = new MyDataSource();
...
}

Related

Problem with local variable - Global variable flutter dart

I want to pass dynamic data through different pages, when using a BottomAppBar. I currently switch between pages/widgets my storing them like this:
#override
void initState() {
super.initState();
final _pageOptions = [
SwipeScreen(currentUserId: currentUserId),
Requests(currentUserId: currentUserId),
Messages(currentUserId: currentUserId),
Settings(),
];}
I then use _pageOptions in my Scaffold:
body: _pageOptions[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
As you may have guessed, I can't use _pageOptions as my body in my Scaffold, as it is a local variable when wrapped in an initState(). I have to use this initState though, as without it, I can only pass static members in my initializers.
I don't know how to fix this, as removing one just gives me a different error. I have looked for ways to make a variable global, for example having _pageOptions in a different file, but then it was still local, and therefore not defined when used in my Scaffold.
I hope my problem is clear.
Thanks in advance.
This isn't trying to be mean, just honest so please don't take offence, but from the sounds of things you need to start over with some of the basic tutorials on how flutter works and how to develop things in flutter. I'd recommend working through a couple of the tutorials & codelabs on the flutter website from start to finish (without looking at the final code until you're) and then seeing what you've done differently from them. And maybe even the getting started with Dart documentation...
The simplest answer to your problem is to simply make _pageOptions a member variable of your class as that will allow you to access it from wherever else you need. But since you're running into the issue of only being able to "pass static members in my initializers", that probably means that you aren't initializing things in the right place.
Here's a few things to keep in mind when developing with flutter:
You shouldn't be creating widgets anywhere except the build function (unless you make a helper function called from the build function - but if you're doing that, it's probably because the widget is getting big and you should split it out into its own Stateful or Stateless widget)
You shouldn't inherit widgets unless you know what you're doing. Most of the time you'll only inherit StatelessWidget and StatefulWidget, and everything else is done through encapsulation (i.e. using them in the build function)
Simply switching pages by changing what you build using an array isn't a great way of doing it - you don't get things like animations when navigating, you potentially build things sooner than needed, and lose out on things like the state of the page unless you're careful about how you do it.
Building widgets is cheap in flutter and you should generally be building most of them for each and every page. There are situations where this isn't true but for a basic app, build the navigation bar for each screen (but with the actual logic that builds it split out into a Stateless or Stateful widget!)
The solution for #3 is to use a flutter's navigation and either define your pages in MaterialApp(routes: ...) or MaterialApp(onGenerateRoute: ...). So you'd be setting up routes for SwipeScreen, Requests, Messages, and Settings. You'd use Navigator.push or Navigator.pushNamed, or Navigator.pop to move between them.
And so that you're not copying and pasting the bottom navigation bar everywhere, split it out into its own widget i.e. MyBottomNavigationBar extends StatelessWidget. And then in each of the pages you'd have a Scaffold(body: ..., bottomNavigationBar: MyBottomNavigationBar()). If your scaffold gets really complicated you could even move it to its own widget too.
Oh and I've just read what might be an important part of your question: I want to pass dynamic data through different pages. Using the navigator as I described changes this slightly as you don't want to be passing it down through each layer of build.
There's various ways of getting around this - the most basic is using an InheritedWidget, but as it needs a lot of boilerplate to make it work I recommend using a ScopedModel. That way, you simply have to make a class inherited from Model (i.e. UserModel), and then change the information within the model and notify listeners (i.e. the userId) when the user is chosen/logged in. Something like this:
class UserModel extends Model {
String _userId;
String get userId => _userId;
void setUser(String userId) {
_userId = userId;
notifyListeners();
}
static CounterModel of(BuildContext context, {bool rebuildOnChange = false}) =>
ScopedModel.of<CounterModel>(context, rebuildOnChange = rebuildOnChange);
}
You'd need to put that somewhere high in your widget tree (probably above the MaterialApp or in the MaterialApp(builder: ...)). And you could then add name, profile, color, etc to that model, and use all of that from wherever you need in your app.
Do you want to use _pageOptions in the same class ? if it is this should solve your problem.
var _pageOptions;
// declare a variable inside your class/outside of your initState to reach it from anywhere inside in your class
#override
void initState() {
super.initState();
_pageOptions = [
SwipeScreen(currentUserId: currentUserId),
Requests(currentUserId: currentUserId),
Messages(currentUserId: currentUserId),
Settings(),
];
}

How do you create Services/Providers in Flutter?

Let's say I need a user service which has all my user variables and functions, and another item service with all item variables and functions, etc etc. I want all this data and functions available throughout the project, passing the user data where it's needed, for example.
I suspect it has something to do with inherited widgets, but how does that work? Like, I see how I could use one inherited widget at the root level, but am I supposed to build a bunch of inherited widgets at the root-level for each service? Or just put ALL the data in the one top inherited widget? Seems like that may get messy. I have yet to see an example of this.
... or should I just be using a class with static variables and call on that where I need it?
See the link provided by Günter above. If you have thirty minutes watch Brian Egan's DartConf 18 presentation: Keep it Simple, State. Also see Brian's example code here. He shows the same app coded using InheritedWidget with a Controller, and with Flutter Redux.
Here's a example of a single InheritedWidget from a simple app of mine...
class StateContainer extends InheritedWidget {
final List<Membership> memberships;
final IntFunction getNextIndex;
final VoidMembershipFunction updateMembership;
final VoidIntFunction deleteMembership;
const StateContainer({
this.memberships,
this.getNextIndex,
this.updateMembership,
this.deleteMembership,
Widget child,
})
: super(child: child);
static StateContainer of(BuildContext context) {
return context.inheritFromWidgetOfExactType(StateContainer);
}
#override
bool updateShouldNotify(StateContainer oldWidget) {
return true;
}
}
I pass in my MaterialApp as the child. A high proportion of the tree then becomes stateless as it has access to the data and methods if the StateContainer. You could think of these as your model and controller in one. Build methods in the tree often start with...
#override
Widget build(BuildContext context) {
final StateContainer container = StateContainer.of(context);
to get access to them.
You are right that a single InheritedWidget becomes unwieldy quickly - which is where you might invest in exploring Redux - see the last 10 minutes of the talk. In my experience, the area that gets messiest quickest is the updateShouldNotify method when ends up comparing all the member variables. (In this simple example there is only one member variable, and setState only gets called when it changes, so it's trivially always true.)

The best way to passing data between widgets in Flutter

I develop my own custom widget which is used in some other view. In this custom widget, I have one class property which stores information, let's say that this is a list which gains new items inside the widget. Now I want to get items from this list from the level of my main widget.
How to do that?
I don't want to create a variable like this: var customWidget = MyCustomWidget() and then, get the inside variable like customWidget.createState().myList - I think this is a terrible solution (and I'm not sure if it will work). Also passing a list to the constructor of my custom widget looks very ugly.
Is there any other way to get other widget's state?
First, bear in mind that in Flutter data can only be passed downward. It is by design not possible to access data of children, only parents (although there are some hacks around it).
From this point, there are 2 main solutions to pass data:
Add the wanted data to your widgets constructors.
I don't think there is much to say here. Easy to use. But boring when you want to pass one field to all your widget tree. Use this method only when the scope of a value is limited. If it's something like configurations or user details, go for the second solution.
class Bar extends StatelessWidget {
final String data;
Bar({this.data});
#override
Widget build(BuildContext context) {
return Text(data);
}
}
Using Flutter's BuildContext
Each widget has access to a BuildContext. This class allows one widget to fetch information from any of their ancestors using one of the following methods:
inheritFromWidgetOfExactType(Type)
ancestorStateOfType(TypeMatcher)
ancestorWidgetOfExactType(Type)
...
As a matter of facts, if there's a data that needs to be accessed many times, prefer inheritFromWidgetOfExactType.
This uses InheritedWidget; which are specifics kind of widgets that are extremely fast to access to.
See Flutter: How to correctly use an Inherited Widget? for more details on their usage
As a side note, there a third solution. Which is GlobalKey
I won't go into too many details, as this is more a hack than a proper solution. (see Builder versus GlobalKey)
But basically, it allows getting the state/context of any widgets outside of the build call. Be it parents or children.
on my side I implemented onChanged callback.
For example In my Widget :
class ObjectiveCardWidget extends StatefulWidget {
final Objective? objective;
final ValueChanged<Objective?>? onChanged;
ObjectiveCardWidget({this.objective, this.onChanged});
#override
State<ObjectiveCardWidget> createState() => _ObjectiveCardWidgetState();
}
when my data is updated un my custom widget I just called :
widget.onChanged!(newValue); // newvalue has been set previously
In my parentWidget I used onChanged as usual:
ObjectiveCardWidget(
objective: myObjective,
onChanged: (value) {
setState(() {
myObjective = value;
});
})
Hope it will help.

Which Widgets should I make stateful?

So imagine I have an app (standard material scaffold) with a drawer, app bar, body etc.
I'm now thinking what is the correct way to implement it, given I essentially don't want any state in the widgets (i.e. I want to keep state (data) in dedicated model/store/controller classes and keep widgets only responsible for UI/pixels).
I could make the top-level, root app widget stateful and then setState((){}) (note the dummy callback, which I'd use just to trigger the rebuild) whenever the controller(s) tell me to. The child widgets would then get rebuilt and would read the values they're interested in from the stores/models/...
I could make the top-level widget stateless and only mark the leafs as stateful.
Given Flutter claims to be optimized for frequent Widget rebuilds, is one option significantly better than the other? I'd bet the leaf approach might be more performant, but it's much more verbose (2x the classes).
I'm now thinking what is the correct way to implement it, given I essentially don't want any state in the widgets (i.e. I want to keep state (data) in dedicated model/store/controller classes and keep widgets only responsible for UI/pixels).
The correct way to implement it imo is how the flutter team designed the state class to be used. You are injecting a state object into your UI class when you do this:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {...}
I.e only state data relevant to the widget and/or its direct child widgets are stored in the widget's state.
It's a bad idea (complexity) wise to track state in only 1 (the topmost) widget. Seeing that the framework has a lot of ground to cover when comparing previous to current state on setState() when the state object is big. It seems like a waste to not make use of smaller state objects that change independently. Also, from a maintenance point of view it's easy to get context for what you're looking at when the state object is relevant to only the current widget.
It seems to me that you are fighting the framework, can you elaborate on a specific reason/example why you wouldn't want any state in widgets?

Dart initializer list and access of recently filled instance variable

I am trying to initialize some event stream in a class. I want that stream to be final, but controlled by StreamController. I have tried following code:
import "dart:async";
class Dog {
final StreamController _onBarkController;
final Stream onBark;
Dog() :
_onBarkController = new StreamController(),
onBark = _onBarkController.stream;
}
But this code is illegal, because the access (even implicit) to this is forbidden in the initializer list.
Is there any way how to achieve this?
There isn't a great way to solve the general problem of needing to destructure some object into multiple final fields, which is basically what you're attempting here. But the good news is that usually you don't really need to. The two approaches I would recommend are factory constructors and not keeping derived state.
Factory constructors are great because you can perform arbitrary computation to create your arguments before calling the real constructor, which can usually only have an initializer list. In this case you can have a factory constructor create the StreamController and pass it and the stream to a private constructor.
Even better for you though, would be to not store the Stream in a field because you can get to it via the controller. I do this all the time with streams:
class Dog {
final StreamController _onBarkController = new StreamController();
Stream get onBark => _onBarkController.stream;
}
onBark is really a value derived from _onBarkController, so there's no need to store it.

Resources