Flutter: keep widget alive (streambuilder) - flutter

I have a flutter app with a Pageview (and at the bottom a cupertinotabbar). Everything 'works'. But when I switch pages, the streambuilders 'glitch'. (There's one on the first page and on the second page) (see video).
I think the problem is that flutter 'destroys' the streambuilders and then reloads them.
I tried putting keepClientAlive on the statefull widget, but didn't work.
Is there a possibility to keep the streambuilder alive?
This is my code:
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
body: PageView(
children: <Widget>[
Discover(currentUser: currentUser),
Friends(currentUser: currentUser),
Find(),
],
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: CupertinoTabBar(
backgroundColor: Colors.black,
currentIndex: pageIndex,
onTap: onTap,
activeColor: Colors.green,
items: [
BottomNavigationBarItem(icon: Icon(FontAwesomeIcons.search)),
BottomNavigationBarItem(icon: Icon(FontAwesomeIcons.users)),
BottomNavigationBarItem(icon: Icon(FontAwesomeIcons.biking)),
],
),
),
);
}
My streambuilder class
class BookList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
}
},
);
}
}
https://www.youtube.com/watch?v=y2yKhqvkT_A&feature=youtu.be
Thanks!

Related

How to refresh Widget in CupertinoTabScaffold tabBuilder Flutter?

I have a tabBuilder with switch case, I have a tab that needs to be refreshed after authorization or logout. I have a token variable which is assigned a value after authorization. I need to update the widget every time my variable is assigned a value, how can i make it? I also have a way to update the tab every time you go to it, but it will not work here
#override
Widget build(BuildContext context) {
setState(() {
Constants.USER_TOKEN; // my token variable
});
return CupertinoTabScaffold(
controller: _controller,
key: myKey,
backgroundColor: Colors.black87,
tabBar: CupertinoTabBar(
iconSize: 30,
currentIndex: currentIndex,
onTap: onItemTapped,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home, size: 25)),
BottomNavigationBarItem(icon: Icon(Icons.store, size: 25,)),
BottomNavigationBarItem(icon: Icon(Icons.shopping_bag, size: 25)),
BottomNavigationBarItem(icon: Icon(Icons.person, size: 25)),
],
),
tabBuilder: (BuildContext context, int index) {
print(currentIndex);
switch (index) {
case 0:
// The page that should be updated when the variable changes
return
CupertinoTabView(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: HomePage(),
);
},
);
case 1:
return CupertinoTabView(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: CatalogPage(),
);
},
);
case 2:
return currentIndex == 2 ? // my always refresh method
CupertinoTabView(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: ShoppingBag(),
);
},
) : Container();
case 3:
return CupertinoTabView(
builder: (BuildContext context) {
return const CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: UserProfileScaffold(),
);
},
);
default:
return Container();
}
});
}

How do we transfer data from one screen to another screen in flutter?

#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Category',
),
),
body: Center(
child: FutureBuilder(
future: fetchCategory(),
builder: (ctx, snapShot) {
if (snapShot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
return ListView.builder(
itemCount: snapShot.data["table"].length,
itemBuilder: (context, index) {
return ListTile(
title: TextButton(
style: TextButton.styleFrom(
textStyle: TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
child: Text(snapShot.data["table"][index]["name"]),
),
//subtitle: Text("price: ${snapShot.data["table"][index]["price"]}"),
);
},
);
}
},
),
),
);
}
I want to transfer this data (snapShot.data["table"][index]["id"]) and use it to display the results on a second screen / data return String /
I want to use the data to display the items that have the same number on the second page, how can I do that
While you are using pushNamed(context, '/second'); and if receiver widget, don't have Constructor to get data, you can pass through ModalRoute like
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WidgetA(),
settings: RouteSettings(
arguments: "id",
)),
);
and received on second screen as
class WidgetA extends StatelessWidget {
static final routeName = "/widgetA";
#override
Widget build(BuildContext context) {
final data = ModalRoute.of(context)!.settings;
late String retriveString;
if (data.arguments == null)
retriveString = "empty";
else
retriveString = data.arguments as String;
return Scaffold(
body: Column(
children: [
Text("Widget A"),
Text("Got data from parent $retriveString"),
],
),
);
}
}
I will suggest you to visit this where I've described different ways passing data.
you can pass data using argument
onPressed: () {
Navigator.pushNamed(context, '/second', arguments: snapShot.data["table"][index]);
},

Flutter no backbutton on topleft, when calling a listviewScreen from a drawer

im new to Flutter. I have a mainpage, which is a listview with a drawer. I manage to call a second listview, which is mainly a copy of the mainpage without the drawer. But on the secondpage i see no backbutton on the top left.
Here i call the secondpage inside the drawer:
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new ListViewTeam()),
);
},
and my secondpage begins with this code:
#override
_ListViewTeamState createState() => new _ListViewTeamState();
}
class _ListViewTeamState extends State<ListViewTeam> {
List<Team> items = new List();
DatabaseHelper db = new DatabaseHelper();
#override
void initState() {
super.initState();
db.getAllTeams().then((teams) {
setState(() {
teams.forEach((team) {
items.add(Team.fromMap(team));
});
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Teams',
home: Scaffold(
appBar: AppBar(
title: Text('Teams'),
centerTitle: true,
backgroundColor: Colors.teal,
),
body: Center(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.all(1.0),
itemBuilder: (context, position) {
Hope someone can help a noob?
You can try with the Below code replace this code at your second page I hope this will work for you
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Teams'),
centerTitle: true,
backgroundColor: Colors.teal,
),
body: Center(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.all(1.0),
itemBuilder: (context, position) {

What is the use of `rootNavigator` in Navigator.of(context, rootNavigator: true).push();

What's the difference between
Navigator.of(context).pushNamed("/route");
and
Navigator.of(context, rootNavigator: true).pushNamed("/route");
More importantly, what's the use of setting rootNavigator: true on Navigator class, I read docs but they aren't quite clear. Can anyone explain the difference properly?
You can copy paste run full code below
There is a root Navigator above tab navigation
This demo shows open(Navigator.push) a full screen dialog (fullscreenDialog: true) with rootNavigator true/false
picture
rootNavigator = true , fullscreenDialog take all screen and above tab
rootNavigator = false, fullscreenDialog take tab size and inside tab, you can switch between Home and Support tab and see fullscreenDialog is still there
working demo
code snippet
Center(
child: CupertinoButton(
child: const Text(
'Push rootNavigator true',
),
onPressed: () {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute<bool>(
fullscreenDialog: true,
builder: (BuildContext context) => Tab3Dialog(),
),
);
},
),
),
Center(
child: CupertinoButton(
child: const Text(
'Push rootNavigator false',
),
onPressed: () {
Navigator.of(context, rootNavigator: false).push(
CupertinoPageRoute<bool>(
fullscreenDialog: true,
builder: (BuildContext context) => Tab3Dialog(),
),
);
},
),
),
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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,
),
home: PawzHome(),
);
}
}
class PawzHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.conversation_bubble),
title: Text('Support'),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return CupertinoTabView(
builder: (BuildContext context) {
return CupertinoDemoTab1();
},
defaultTitle: 'Colors',
);
break;
case 1:
return CupertinoTabView(
builder: (BuildContext context) => CupertinoDemoTab2(),
defaultTitle: 'Support Chat',
);
break;
}
return null;
},
);
}
}
class CupertinoDemoTab1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(),
SliverList(
delegate: SliverChildListDelegate([Tab1RowItem()]),
),
],
),
);
}
}
class Tab1RowItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).push(CupertinoPageRoute<void>(
title: "Click me",
builder: (BuildContext context) => Tab1ItemPage(),
));
},
child: Padding(padding: EdgeInsets.all(10.0), child: Text("Click me")),
);
}
}
class Tab1ItemPage extends StatelessWidget {
#override
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Container(
child: Column(
children: <Widget>[
SizedBox(height: 100,),
Center(
child: CupertinoButton(
child: const Text(
'Push rootNavigator true',
),
onPressed: () {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute<bool>(
fullscreenDialog: true,
builder: (BuildContext context) => Tab3Dialog(),
),
);
},
),
),
Center(
child: CupertinoButton(
child: const Text(
'Push rootNavigator false',
),
onPressed: () {
Navigator.of(context, rootNavigator: false).push(
CupertinoPageRoute<bool>(
fullscreenDialog: true,
builder: (BuildContext context) => Tab3Dialog(),
),
);
},
),
),
],
),
));
}
}
class CupertinoDemoTab2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Container(
child: Center(
child: Text("Tab 2"),
),
));
}
}
class Tab3Dialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: CupertinoButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text("Ok"),
),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: const Text('Sign in'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
If your app has nested navigators this parameter comes in handy when you want to call the root navigator not it's nested navigators ... please consider the image bellow ... in this example we are inside the page 2 and we want the page 3 to be the rootNavigator's child (because for example we want to ignore the bottomNavigationBar that is in MainPage)... in this example if you don't set the rootNavigator = true and you push the page 3 it will be the nestedNavigator's child (So the BottomNavigationBar of the MainPage's will be still visible).

Flutter returning multiple widgets stuck

I am working on a Flutter widget but I can't seem to get it to return multiple widgets. The Widget is called Widg and it should return the listView.builder widget and the floatingActionButton widget. Here is my code:
#override
Widget build(BuildContext context) {
return <Widget>[
//children: <Widget> [
ListView.builder(
itemCount: list.length,
itemBuilder: (context, i) {
return listRow();
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
list.add(list.length);
});
}
)
]
];
}
I am unable to figure out how to do this. I tried listing them as children as per the comment, but it didn't work. This is where I call my widget:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Next Page"),
),
body: Widg()
);
}
Can someone please help me out? Thanks!
This should work.
FloatingActionButton documentation for your reference.
import 'package:flutter/material.dart';
Widget build(BuildContext context) {
List list;
return new Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
list.add(list.length);
});
}),
appBar: new AppBar(
title: new Text("Next Page"),
),
body: ListView.builder(
itemCount: list.length,
itemBuilder: (context, i) {
return listRow();
},
),
);
}