Flutter. How to update text inside GridView which inside PageView? - ios

I have a Page that contains a PageView and this PageView contains a GridView, and this GridView contains Text. Right now I want to update only one Text, not all the Text, how should I do without reloading all the PageView? For example, right now I only have a Text(), I want to make a widget out of this. How should I do that so I can update the specific Text and not reload all PageView and its children?
PageView.builder(itemBuilder: (context, index) {
return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, ), itemBuilder: (context, index) {
return Text('$index');
});
}, itemCount: 3,),
)

My answer for you: unnecessary. Because GridView.builder cache your widget. You can change your text comfortably and setState in current Page. GridView will build the widgets have changed.

It depends how you get your updated data.
You can customize the GridView Item with a customized Widget, and call setState in that widget.
But you need a way to know when the data been updated.
class YourGridViewCell extends StatefulWidget {
final String label;
const YourGridViewCell({this.label});
#override
State<StatefulWidget> createState() => _YourGridViewCellState();
}
class _YourGridViewCellState extends State<YourGridViewCell> {
String _label;
#override
void initState() {
super.initState();
_label = widget.label;
}
#override
Widget build(BuildContext context) {
return Text(widget.label);
}
// you can call this to update cell.
void _updateText(String newLabel) {
setState(() {
_label = newLabel;
});
}
}

Related

How to fix FutureBuilder open multiple times error?

these my two classes(two pages). these two classes open multiple times.
I put debug point in futurebuilder in two classes.
debug point running,
MainCategory page and got to the next page
SubCategory page and again running MainCategory page(previous page) futurebuilder and again running MainCategory page futurebuilder
navigate subcategory page to third page running subcategory page and main category page
I upload my two classes to GitHub and please let me know what the issue is.
MainCategory code: https://github.com/bhanuka96/ios_login/blob/master/MainCategory.dart
SubCategory code: https://github.com/bhanuka96/ios_login/blob/master/subCategory.dart
As stated in the documentation, you should not fetch the Future for the Futurebuilder during the widget's build event.
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
So, try to move your call to getRegister method outside the build method and replace it with the returned Future value.
For example, below I have a class that returns a Future value which will be consumed with the help of FutureBuilder.
class MyApiHelper{
static Future<List<String>> getMyList() async {
// your implementation to make server calls
return List<String>();
}
}
Now, inside your widget, you will have something like this:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myList;
#override
void initState() {
super.initState();
_myList = MyApiHelper.getMyList();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: _myList,
builder: (_, AsyncSnapshot<List<String>> snapLs) {
if(!snapLs.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapLs.data.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
},
));
}
}
As shown above, the Future is fetched in the initState function and used inside the build method and used by FutureBuilder.
I hope this was helpful.
Thanks.
If you happen to use Provider, here's (in my opinion) a clearer alternative based on your question:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureProvider<List<String>>(
create: (_) => MyApiHelper.getMyList(),
child: Consumer<List<String>>(
builder: (_, list, __) {
if (list == null) return CircularProgressIndicator();
return ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
};
),
);
}
}
This can also be achieved of course as a StatefulWidget as suggested by the other answer, or even with flutter_hooks as explained in Why is my Future/Async Called Multiple Times?
You can create new Widget and pass Function to
returnFuture as
() {
return YourFuture;
}
import 'dart:developer';
import 'package:flutter/material.dart';
class MyFutureBuilder<T> extends StatefulWidget {
final Future<T> Function() returnFuture;
final AsyncWidgetBuilder<T> builder;
final T initialData;
MyFutureBuilder({
this.returnFuture,
#required this.builder,
this.initialData,
Key key,
}) : super(key: key);
#override
_MyFutureBuilderState<T> createState() => _MyFutureBuilderState<T>();
}
class _MyFutureBuilderState<T> extends State<MyFutureBuilder<T>> {
bool isLoading = false;
Future<T> future;
#override
void initState() {
super.initState();
future = widget.returnFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: widget.builder,
initialData: widget.initialData,
future: future,
);
}
}
Example
MyFutureBuilder<List<User>>(
returnFuture: () {
return moderatorUserProvider
.getExecutorsAsModeratorByIds(val.users,
save: true);
},
builder: (cont, asyncData) {
if (asyncData.connectionState !=
ConnectionState.done) {
return Center(
child: MyCircularProgressIndicator(
color: ModeratorColor.executors.color,
),
);
}
return Column(
children: asyncData.data
.map(
(singlExecutor) =>
ChooseInfoButton(
title:
'${singlExecutor.firstName} ${singlExecutor.secondName}',
subTitle: 'Business analyst',
middleText: '4.000 NOK',
subMiddleText: 'full time',
label: 'test period',
subLabel: '1.5 month',
imageUrl:
assetsUrl + 'download.jpeg',
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
ModeratorExecutorEditPage(),
),
);
},
),
)
.toList());
},
)
```

Flutter: set parent widget state from child widget

I am very beginner to Flutter and Dart. So I am trying to update the state of the parent widget, but to be honest after trying many different solutions none worked for me, or am I doing something wrong?
What I'm trying to do is to update the _title in _BooksState() when the page changes in _Books() class.
How do I set the _title state from the child (_Books()) widget?
class Books extends StatefulWidget {
#override
_BooksState createState() {
return _BooksState();
}
}
class _BooksState extends State<Books> {
String _title = 'Books';
_setTitle(String newTitle) {
setState(() {
_title = newTitle;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
body: _Books(),
);
}
}
class _Books extends StatelessWidget {
final PageController _controller = PageController();
final Stream<QuerySnapshot> _stream =
Firestore.instance.collection('Books').orderBy('title').snapshots();
_setAppBarTitle(String newTitle) {
print(newTitle);
// how do I set _title from here?
}
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _stream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
final books = snapshot.data.documents;
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
return PageView.builder(
controller: _controller,
scrollDirection: Axis.horizontal,
itemCount: books.length,
itemBuilder: (context, index) {
final book = books[index];
return ListTile(
title: Text(book['title']),
subtitle: Text(book['author']),
);
},
onPageChanged: (index) {
_setAppBarTitle(books[index].data['title']);
},
);
}
},
);
}
}
let me repeat your question in other words: You want to setstate a widget(or refresh a page, or change a variable 'binded' to a widget) when something happens(not inside the same class of the widget).
This is a common problem for all newbies in flutter(including me), which is called state management.
Of course you can always put everything inside the same dart file, or even the same class, But we don't do that for larger app.
In order to solve this problem, I created 2 examples:
https://github.com/lhcdims/statemanagement01
This example uses a timer to check whether something inside a widget is changed, if so, setstate the page that the widget belongs to.
try to take a look at the function funTimerDefault() inside main.dart
Ok, this was my first try, not a good solution.
https://github.com/lhcdims/statemanagement02
This example's output is the same as 1, But is using Redux instead of setState. Sooner or later you'll find that setstate is not suitable for all cases(like yours!), you'll be using Redux or BLoC.
Read the readme inside the examples, build and run them, you'll then be able to (refresh) any widget(or changes variables binded to a widget), at any time(and anywhere) you want. (even the app is pushed into background, you can also try this in the examples)
What you can do is move you _Books class inside the _BooksState class..
And instead of using _Books as class you can use it as Widget inside _BooksState class so that you can access the setState method of StatefulWidget inside the Widget you create.
I do it this way and even I'm new to Flutter and Dart...This is working for me in every case even after making an API call..I'm able to use setState and set the response from API.
Example:
class Books extends StatefulWidget {
#override
_BooksState createState() {
return _BooksState();
}
}
class _BooksState extends State<Books> {
String _title = 'Books';
_setTitle(String newTitle) {
setState(() {
_title = newTitle;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
body: _books(), // Using the Widget here
);
}
// Your `_Books` class created as `Widget` for setting state and new title.
Widget _books() {
final PageController _controller = PageController();
final Stream<QuerySnapshot> _stream =
Firestore.instance.collection('Books').orderBy('title').snapshots();
_setAppBarTitle(String newTitle) {
print(newTitle);
// how do I set _title from here?
// Since you created this method and setting the _title in this method
// itself using setstate you can directly pass the new title in this method..
_setTitle(newTitle);
}
return StreamBuilder<QuerySnapshot>(
stream: _stream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
final books = snapshot.data.documents;
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
return PageView.builder(
controller: _controller,
scrollDirection: Axis.horizontal,
itemCount: books.length,
itemBuilder: (context, index) {
final book = books[index];
return ListTile(
title: Text(book['title']),
subtitle: Text(book['author']),
);
},
onPageChanged: (index) {
_setAppBarTitle(books[index].data['title']);
},
);
}
},
);
}
}

How to detect whether the route is the current route in its build method

I use FutureBuilder in my routes, which displays data after getting data from database.
I found that when I open the second route, the build method of home route was called even home route is not the current route. However, I hope the build method does not get data if home route isn't the current one.
That's the code I try to implement:
class HomeRoute extends StatefulWidget { State<StatefulWidget> createState() => HomeRouteState(); }
class HomeRouteState extends State<HomeRoute> {
#override
Widget build(BuildContext context) => Scaffold(
//...
drawer: Drawer(
// There is a ListTile that can push SecondRoute
),
body: FutureBuilder(
future: _getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {/* ... */}
),
);
_getData() async {
if(/* this route is the current one */) {
// get data
}
}
}
So basically you want to prevent the build() of your home page from getting called when it is not visible. Here is what you can try.
bool _isHomeVisible = true;
#override
Widget build(BuildContext context) {
return _isHomeVisible ? YourWidgetImplementation() : Container();
}
And before you navigate do this.
void _navigateToNewPage() {
_isHomeVisible = false; // going to new page, make it false
Navigator.push(...).then((_) {
_isHomeVisible = true; // coming back to home page, make it true
});
}

Persist Flutter widget in memory

I've created my own simple bottom nav bar implementation in Flutter. When a tab is pressed, Flutter is currently re-creating the widget (initState() gets called every time) which is non-desirable.
I want the widgets to be persisted in memory so if they've already been created, they're simply popped straight in.
Main Widget
class _MainRootScreenState extends State<MainRootScreen> {
int _selectedIndex = 0;
List<Widget> _screens;
#override
void initState() {
// load pages
_screens = [
PageOne(),
PageTwo(),
PageThree()
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_selectedIndex],
bottomNavigationBar: _buildBottomTabBar(context)
);
}
}
so when _selectedIndex gets updated, the selected page is getting re-created.
I've tried using AutomaticKeepAliveClientMixin on the pages with no luck.
If you want that your widget/page should not rebuild when you click on tab button. You just need to follow this code
just add State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> to your state class. after this you need to override a method called wantKeepAlive and make wantKeepAlive as true that's it.
By default wantKeepAlive is false because of it saves our memory .
PageOne
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> {
// Your code are here
#override
bool get wantKeepAlive => true;
}
Do the same from pageTwo and PageThree also that's it

flutter list of clickable images

I have a good listview working with a leading image, title and its clickable. I wanted to try and get it to be just a list of images with text on top of it. I have looked at gridview, but really just need 1 image per line. This is my listview code. Can this be changed or do I need to rewrite it to make this work.
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new RefreshIndicator(
child: new ListView.builder(
itemBuilder: _itemBuilder,
itemCount: mylist.length,
),
onRefresh: _onRefresh,
));
}
Widget _itemBuilder (BuildContext context, int index) {
Specialties spec = getSpec(index);
return new SpecialtyWidget(spec: spec,);
}
Specialties getSpec(int index) {
return new Specialties(mylist[index]['id'], mylist[index]['name'], mylist[index]['details'], new Photo('lib/images/'+mylist[index]['image'], mylist[index]['name'], mylist[index]['name']));
//return new Specialties.fromMap(mylist[index]);
}
class SpecialtyWidget extends StatefulWidget {
SpecialtyWidget({Key key, this.spec}) : super(key: key);
final Specialties spec;
#override
_SpecialtyWidgetState createState() => new _SpecialtyWidgetState();
}
class _SpecialtyWidgetState extends State<SpecialtyWidget> {
#override
Widget build(BuildContext context) {
return new ListTile(
leading: new Image.asset(widget.spec.pic.assetName),
//title: new Text(widget.spec.name),
onTap: _onTap,
);
}
void _onTap() {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/specs/spec"),
builder: (BuildContext context) => new SpecPage(spec: widget.spec),
);
Navigator.of(context).push(route);
}
}
If it will not work with the listview any guidance would be helpful.
Thanks in advance
I'm having a hard time understanding your question, but it sounds like you want the entries in your list to use a different layout than the one provided by ListTile.
You could use a Stack to put text on top of your images (compositing them together) or a Column if you want to put text vertically above your images. You can also use other Flutter layout widgets to ensure that the text appears in the right place.

Resources