Best way to pass data from a root to deeper child widget in Flutter - flutter

I am new to flutter. My flutter widget tree is getting deeper and deeper, I want to know which is the best method to pass data from a root to a widget which is much deeper from it. I'm currently passing it as a constructor from widget to widget.
My current implementation is given below
Level1(data: data)
Level2(data: data)
Level3(data: data)
Level4(data: data)
suppose my data is retrieved from DB in level1 widget and it is used in level4 widget. As we see, my current implementation is considerably messy. How this is generally done? what is the best practice?

You might like to use Provider. You can find more about it here.
Basically, you create provider of the data at the top-most level like:
class Level1 {
#override
Widget build(BuildContext context) {
Provider<Data>(
create: (_) => Something(),
child: Level2 (
// stuff of level 2
),
),
}
}
Something in this case bight be a change notifier.
You can then access it at a lower level with:
final provider = Provider.of<Something>(context);

Inherited widget - If you want to avoid using any third party library..
More can be found here - https://medium.com/#mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
and here https://www.youtube.com/watch?v=Zbm3hjPjQMk
class MyInheritedWidget extends InheritedWidget {
final int accountId;
final int scopeId;
MyInheritedWidget(accountId, scopeId, child): super(child);
#override
bool updateShouldNotify(MyInheritedWidget old) =>
accountId != old.accountId || scopeId != old.scopeId;
}
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyInheritedWidget(
accountId,
scopeId,
const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget();
Widget build(BuildContext context) {
// somewhere down the line
const MyOtherWidget();
...
}
}
class MyOtherWidget extends StatelessWidget {
const MyOtherWidget();
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
print(myInheritedWidget.scopeId);
print(myInheritedWidget.accountId);
...

Related

Difference between passing: method that returns a widget, a unique widget class, or a Widget object

I am confused between these three ways of passing/building widgets:
1.
Widget _myDisplay() {
return [widgets showing content];
}
#override
Widget build(BuildContext context) {
return _myDisplay();
}
#override
Widget build(BuildContext context) {
return MyDisplay();
}
where MyDisplay is defined as such (I'm not sure if it's crucial whether MyDisplay is a StatelessWidget or a StatefulWidget):
class MyDisplay extends StatelessWidget {
const MyDisplay({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return [widgets showing content];
}
}
Widget _myDisplay = [widgets showing content];
#override
Widget build(BuildContext context) {
return _myDisplay;
}
I've read this thread comparing the first two methods, and from what I understand, using a unique, named class extending StatelessWidget or StatefulWidget allows you to use the const keyword which signifies that it will not be rebuilt when the Widget tree is rebuilt.
However, what about the 3rd method above? Is it the same as 1. or 2., or completely different? If so, how is it different and when is it preferred?
Thanks!
Type 1: helper methods
Type 2: Widgets
Type 3: Variables
For type 1 and 2, I will highly recommend to check Widgets vs helper methods | Decoding Flutter
Helper methods will rebuild everything on every state changes which can be heavy based on scenario like using animated-widgets.
Try using Widget with const constructor to get better performance,
Now for the 3rd type variable. It is totally different from helper method and widgets. This variable won't change until you handle the variable state. Test this example code, and you will find the issue where data doesn't change on type3(variable) case.
class Test3 extends StatefulWidget {
const Test3({Key? key}) : super(key: key);
#override
State<Test3> createState() => _Test3State();
}
class _Test3State extends State<Test3> {
String data = "A";
late Widget _myDisplay = Text("$data");
Widget _myDisplayMethod() => Text("$data");
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
data = "b";
});
}),
body: Center(
child: Column(
children: [
_myDisplay, //variables doesn't update state(based on cases)
_myDisplayMethod(),
],
),
),
);
}
}

How do I access an variable from a StatefulWidget inside an StatelessWidget?

How do I access the variable "selectedTag" from this statefulWidget:
class _AlertDialogOneState extends State<AlertDialogOne> {
Item selectedTag;
...
}
}
inside this statelessWidget :
class CardTile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(...
Pass it as parameter,
class CardTile extends StatelessWidget {
final Item selectedTag;// Add this
CardTile(this.selectedTag); // Add this
#override
Widget build(BuildContext context) {
return Container(...
To pass this variable, you have multiple ways:
Pass it as a constructor when u navigate to this class using your navigator
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CardTile(selectedTag)),
);
class CardTile extends StatelessWidget {
Item selectedTag;
CardTile(this.selectedTag);
#override
Widget build(BuildContext context) {
return Container(...
Use a state management like provider
class ProviderData with ChangeNotifier {
Item selected;
void changeSelection(newSelect) {
selected = newSelect;
changeNotifier();
}
}
and inside any class you need call this:
final providerData = Provider.of<ProviderData>(context);
so you can access the variable or change it using this instance like this:
final variable = providerData.selected;
providerData.changeSelection(newValue);
print(variable);
hope this help but i see that it is better to pass it through the constructor if you are not using a state managemnt, however i just gave you an example for illustration

How to get StreamProvider data in children with new routes in Flutter (Dart)

I am using the StreamProvider method to wrap my widgets with certain data, such as Auth (which is working anywhere in my app) from Firebase Auth. I want to do the same with a Firestore value but it only seems to work one level deep.
I have a database call that finds an employees profile once the auth check is done. When I try get the employee from my Home() widget with Provider.of(context) it works great:
This is my wrapper widget (which is my main file's home: widget)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user.uid);
// Return either home or authenticate widget
if (user == null) {
return Authenticate();
}
else {
return StreamProvider<Employee>.value(
value: DatabaseService().linkedEmployee(user.uid),
child: Home(),
);
}
}
}
The Database Service function from DatabaseService():
// Get Linked Employee
Stream<Employee> linkedEmployee(String uid) {
return employeesCollection.where("linkedUser", isEqualTo: uid).snapshots().map(_linkedEmployeeFromSnapShot);
}
Employee _linkedEmployeeFromSnapShot(QuerySnapshot snapshot) {
final doc = snapshot.documents[0];
return Employee(
eId: doc.data["eId"],
employeeCode: doc.data["employeeCode"],
fName: doc.data["fName"],
lName: doc.data["lName"],
docId: doc.documentID
);
}
I can access Provider.of<User>(context) from any widget anywhere in my tree. So why can't I do the same for Provider.of<Employee>(context) ?
When I try that in any widget other than Home() I get the error:
Error: Could not find the correct Provider above this Vehicles Widget
For example, in my widget Vehicles:
class Vehicles extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
final employee = Provider.of<Employee>(context);
...
The User Provider works fine, I can print it out, but the employee provider does not work.
Is it something to do with context? Thanks, any advice would be appreciated.
How I'm navigating to the Vehicles() widget from Home() with a raised button with this event :
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Vehicles())
);
},
Here is a more explained reply hence I think some encounter this issue and I also think it's a bit tricky to get the head around it, especially when you have rules in your Firestore that requires a user to be authorized to access the database.
But generally, you want to wrap providers (that you want to access around all of the app) around MaterialApp().
So I'll show you a simple example to easier understand it.
//The App() handles makes the providers globally accessible
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FirebaseAuthProviderLayer(
child: AuthorizedProviderLayer(
authorizedChild: MatApp(child: StartSwitch()),
unAuthorizedChild: MatApp(child: SignInScreen()),
),
);
}
}
//The MaterialApp Wrapped so that it not has to be rewritten
class MatApp extends StatelessWidget {
Widget child;
MatApp({this.child});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
home: child,
);
}
}
class FirebaseAuthProviderLayer extends StatelessWidget {
final Widget child;
FirebaseAuthProviderLayer({this.child});
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: FirebaseAuth.instance.authStateChanges(),
child: child,
);
}
}
//And the layer that decides either or not we should attach all the providers that requires the user to be authorized.
class AuthorizedProviderLayer extends StatelessWidget {
Widget authorizedChild;
Widget unAuthorizedChild;
AuthorizedProviderLayer({this.unAuthorizedChild, this.authorizedChild});
User user;
final FirestoreService firestoreService =
FirestoreService(); //The Service made to access Firestore
#override
Widget build(BuildContext context) {
user = Provider.of<User>(context);
if (user is User)
return MultiProvider(
providers: [
StreamProvider<FirestoreUserData>.value(
value: firestoreService.streamUser(),
),
StreamProvider<AppSettings>.value(
value: firestoreService.streamSettings(),
initialData: null,
)
],
child: authorizedChild,
);
return unAuthorizedChild;
}
}

Initialize StateProvider in Widget

I just want initialize provider only one time with widget param. For some reason I can't use .family. I'm not sure if this is the right way. Can you check this? Thank you.
StateProvider<String> valueStateProvider;
class Widget extends HookWidget {
final String value;
Widget({#required this.value}) {
valueStateProvider = StateProvider<String>((ref) => this.value);
}
}
Finally I found the right way to do this. There is special provider type for this situation. ScopedProvider.
final scopedProvider = ScopedProvider<String>((_) => throw UnimplementedError());
class Widget extends HookWidget {
Widget({#required this.value});
final String value;
#override
Widget build(BuildContext context) {
return ProviderScope(
overrides: [scopedProvider.overrideWithValue(this.value)],
child: AnotherWidget()
);
}
}
So you can use scopedProvider in AnotherWidget. Yay!

Handling variables for stateful widget

I have ListView widget whose contents are loaded dynamically.
So I decided to make myStatelessWidget.
My basic ideas are
Keep variable articles to be shown on ListView in the StatefulWidget or State.
Pass the contents from outside.
So for now, I write like this, but it has error.
Is my basic idea is correct? or where should I fix?
//// to pass the argument from outside.
new BodyLayout(articles: myarticles),
////
class BodyLayout extends StatefulWidget {
// List<Article> articles // ???I should put here/?
BodyLayout({articles});
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
// List<Article> articles // ???I should put here/?
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.articles.length, // the getter 'articles' is not defined error....
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.articles[index].title),
onTap: () => onTapped(context,widget.articles[index].url),
);
},
);
}
}
You only need to use a stateful widget if you are going to call the setState() method to rebuild the widget with some new state. One case in which you might do that, if you need to retrieve the list of articles from some api or database call, is to have the widget return a loading indicator if the articles list is null, make the async call to retrieve the articles in the state class's initState() method, and when it is returned, rebuild the widget by calling setState() with the retrieved list of articles. Like this, maybe:
/// to pass the argument from outside.
new BodyLayout(),
///
class BodyLayout extends StatefulWidget {
BodyLayout();
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
List<Article> articles;
bool loading = true;
#override
void initState(){
_getArticles();
}
void getArticles() async {
articles = await Repository.instance.getArticles(); //some async method to retrieve the articles
setState((){
loading = false;
}); // after the articles are retrieved you can call setState to rebuild the widget
}
#override
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
onTap: () => onTapped(context, articles[index].url),
);
},
);
}
}
If you have the list of articles to begin with and don't need to rebuild the list, you can just make that a stateless widget and pass in the list of articles.
The error you indicated that you got, seems to be because articles is not actually defined as a variable for that class. Dart supports multiple syntax options for passing instance variables like this but this is how I would define that variable and ensure that it is being passed in when the widget is created (could be stateless or stateful widget):
//// to pass the argument from outside.
new BodyLayout(articles: myarticles),
////
class BodyLayout extends StatelessWidget {
final List<Article> articles
BodyLayout({this.articles}) : assert(articles != null);
#override
Widget build(BuildContext context){ ... };
}
If you want to convert your widget to a StatelessWidget, then you can just delete the createState and move the stuff in the build method of the state class into the widget class. This works just fine if your widget doesn't maintain an internal state, but if it has interactive elements (like buttons or such) you will want to delegate them to the parent widget caller via a callback.
To define properties for your custom widget, define the fields as final and instantiate them in the class constructor with this.fieldName. For example:
class BodyLayout extends StatefulWidget {
BodyLayout({
this.articles,
this.onArticleTapped,
});
final List<Article> articles; // Defining the articles property
final void Function(String) onArticleTapped; // Defining the on-tapped callback
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.articles[index].title),
onTap: () => onArticleTapped(widget.articles[index].url),
);
},
);
}
}
You can then use it like such:
...
BodyLayout(
articles: [some list of articles],
onArticleTapped: (url) => <do something with url>
),