Rebuild a widgets in searchdelegate flutter - flutter

I want to rebuild the buildSuggestions method that returns a widget that the defined below
and resultInfo getting the result from TMDB API search that a string as an input
how can i recall the resultInfo and rebuild it.
#override
Widget buildSuggestions(BuildContext context) {
if (query.length > 1) {
return ResultInfo(s: query);
}
return ResultInfo(s: "old");
//ResultInfo not updating after the query updates
}
this is the home screen code where searchdelegate is defined and here you can find the code for ResultInfo widget.

UPDATE: After looking at the project, you need to call the getMovies function from the buildSuggestions build method because initState was only getting called once despite the rebuilds. Add this and you're good.
#override
Widget buildSuggestions(BuildContext context) {
if (query != '') {
searchResult.getMovies(query); // do the search from here
return ResultInfo(searchString: query);
}
return ResultInfo(searchString: "old");
}
}
You can leave the search in initState also so you don't get a blank list when you navigate to the search page.
OLD ANSWER:
Any time the query changes in a widget that extends SearchDelegate, buildSuggestions is called. You can verify that by adding a print statement in the buildSuggestions build method.
I see a couple issues here:
class ResultInfo extends StatefulWidget {
final String s;
ResultInfo({Key key, #required this.s}) : super(key: key);
#override
_MovieInfoState createState() => _MovieInfoState(s);
}
class _MovieInfoState extends State<ResultInfo> {
// final String s; don't need this here
_MovieInfoState(this.s);
#override
void initState() {
super.initState();
searchResult.getMovies(widget.s); // access the actual parameter you passed in with widget.s
}
When you pass a value into a stateful widget you don't re-declare the same value again in the stateful part, you access what was passed in with widget.variable. So in your case you were passing in a null value into the getMovies function.
So assuming your stream functionality is all working, that simple change alone should fix your issue.
And in the interest of your code being readable by other people, I suggest a better variable name than s, like query for example because that's what it is. Anyone reading your code has no idea what s is and what it's for until they dig around a bit.
Second issue:
if (query.length > 1) { // won't return results until 2nd character is input
return ResultInfo(s: query);
}
return ResultInfo(s: "old");
}
Maybe its your intention to only return ResultInfo(s: query) until more than one character is input, but if its not, change your condition to this
if (query != '') {
return ResultInfo(s: query);
}
return ResultInfo(s: "old");
}

Related

Statefull widget with Provier rebuild scenario does not work in real life

I thought I understand Flutter well, but sometimes widget building does not happen as I imagine. Could someone explain to me why this approach does not work?
I have a stateful widget that contains a child. In this example, this child is called a Graph. The child starts out as a null; therefore the widget should be an empty container. When data arrives from the provider, it should become a real Graph. When updated data from the provider comes, the Graph widget should be rebuilt with the newest items.
When I try this in reality, I witness Graph changing from null to something, however, it stays always the same, even when the new data arrives.
I imagine this has something to do with immutability?
class Name extends StatefulWidget {
const Name({Key key}) : super(key: key);
#override
State<Name> createState() => _NameState();
}
class _NameState extends State<Name> {
#override
Widget build(BuildContext context) {
Graph currentGraph;
return Consumer<DatabaseProvider>(builder: (context, db, child) {
setNewModelIfNeeded(db);
return currentGraph ?? Container();
});
}
setNewModelIfNeeded(db) {
if(db.graph != currentGraph){
setState(() {
currentGraph = db.graph;
});
}
}
}
UPDATE
When constructing a new graph, adding a value "key" that is different from the previous will make the widget rebuild. E.g.:
currentGraph = Graph(copyValuesFrom: db.graph, key: db.graph.toString());
Use global variables. Cannot call setState during building process. It’s strict especially with Stack widget because Overlay needs all calculation of layouting information at first of building.
update: added sample code to the comment question.
class _NameState extends State<Name> {
Graph? _currentGraph;
#override
Widget build(BuildContext context) {
return Consumer<DatabaseProvider>(builder: (context, db, child) {
setNewModelIfNeeded(db);
return _currentGraph ?? Container();
});
}
setNewModelIfNeeded(db) {
if(db.graph != _currentGraph){
_currentGraph = db.graph;
}
}
}
If you want to pass variables that can change over time to your object, consider using ProxyProvider.

Flutter jsonDecode FlutterSession value is not loading in widget initially. but works on hotload

i am initializing a variable with value from session. but could not print it in the widget. but it is showing after hot load. here is my code :
class _dashboardState extends State<dashboard> {
var logindata;
#override
initState() {
super.initState();
_getSession() async {
logindata = jsonDecode(await FlutterSession().get("login_data"));
}
_getSession();
}
#override
Widget build(BuildContext context) {
print(logindata); // prints null
}
}
Instead of jsonDecode(await FlutterSession().get("login_data"))
if i add any random string or number like
logindata = "Session value";
it prints normally. otherwise on hot load
only i am getting the session value.
what will be the reason?
please do help :(. i am new to flutter.
After following ideas from the comments i have updated the code as follows:
class _dashboardState extends State<dashboard> {
var logindata;
#override
void initState() {
getSessionValue().then((logindata) {
setState(() {
logindata = logindata;
});
});
super.initState();
}
Future<void> getSessionValue() async {
logindata = jsonDecode(await FlutterSession().get("login_data"));
return logindata;
}
#override
Widget build(BuildContext context) {
print(logindata); // first prints null then correct array without hotload.
}
}
here i got first null, then the correct value. but in my case i need the value of an object in the array logindata, that is
logindata["shop_name"] . so in that case i am getting error The method '[]' was called on null. Receiver: null Tried calling: []("shop_name") . What do i do now ? i am really stuck here. :(
Let me explain this first,
lifecycle of State goes like this createState -> initState ->........-> build
so you're right about the order of execution
you're calling getSessionValue() from initState and expecting widget to build right after it, but since getSessionValue() returns a Future after awaiting,
the execution continues and builds the widget not waiting for the returned Future value from getSessionValue(), so it prints null initially, and then when the Future is available youre calling setState and it prints the actual value
there is no notable delay here but the execution flow causes it to behave like this
so what's the solution?... Here comes FutureBuilder to the rescue
it is possible to get different states of a Future using FutureBuilder and you can make changes in the UI accordingly
so in your case, inside build, you can add a FutureBuilder like this,
FutureBuilder(
future: getSessionValue(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return Text("none");
case ConnectionState.waiting: return Text("waiting");
case ConnectionState.active: return Text("active");
case ConnectionState.done:
print(logindata); // this will print your result
return Text("${logindata}");
}
})
keep in mind that the builder should always return a widget
as the async operation is running, you can show the progress to the user by
showing the appropriate UI for different states
for eg: when in ConnectionState.waiting, you can show/return a progress bar
Hope this helps, Thank you
That is a normal behaviour since you are having an async function to get the login data (so it will take some time to be there) while the widget will be building , then build method will get executed first which will make the print executed with no data and then when you hot reload it will be executed perfectly , so if you you want to print it right after you get the data you can make the function this way :
_getSession() async {
logindata = jsonDecode(await FlutterSession().get("login_data")).then((value) {print(value);}); }

How should I implement the init method? In a stateful or stateless widget?

What is the rule of thumb to use an initial method for a widget. Shall I use the:
A. classical stateful widget approach?
Or is it better to stick with the B. stateless widget approach?
Both seem to work from my testing. In terms of code reduction, it seems the B. approach is better, shorter, cleaner, and more readable. How about the performance aspect? Anything else that I could be missing?
Initializing a controller should be a one-time operation; if you do it on a StatelessWidget's build method, it will be triggered every time this widget is rebuilt. If you do it on a StatefulWidget's initState, it will only be called once, when this object is inserted into the tree when the State is initialized.
I was looking for initializing some values based on values passed in constructor in Stateless Widget.
Because we all know for StatefulWidget we have initState() overridden callback to initialize certain values etc. But for Stateless Widget no option is given by default. If we do in build method, it will be called every time as the view update. So I am doing the below code. It works. Hope it will help someone.
import 'package:flutter/material.dart';
class Sample extends StatelessWidget {
final int number1;
final int number2;
factory Sample(int passNumber1, int passNumber2, Key key) {
int changeNumber2 = passNumber2 *
2; //any modification you need can be done, or else pass it as it is.
return Sample._(passNumber1, changeNumber2, key);
}
const Sample._(this.number1, this.number2, Key key) : super(key: key);
#override
Widget build(BuildContext context) {
return Text((number1 + number2).toString());
}
}
Everything either a function or something else in widget build will run whenever you do a hot reload or a page refreshes but with initState it will run once on start of the app or when you restart the app in your IDE for example in StatefulWidget widget you can use:
void initState() {
super.initState();
WidgetsBinding.instance!
.addPostFrameCallback((_) => your_function(context));
}
To use stateful functionalities such as initState(), dispose() you can use following code which will give you that freedom :)
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Function onDespose;
final Widget child;
const StatefulWrapper(
{super.key,
required this.onInit,
required this.onDespose,
required this.child});
#override
State<StatefulWrapper> createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
// ignore: unnecessary_null_comparison
if (widget.onInit != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
if (widget.onDespose != null) {
widget.onDespose();
}
super.dispose();
}
}
Using above code you can make Stateful Wrapper which contains stateful widget's method.
To use Stateful Wrapper in our widget tree you can just wrap your widget with Stateful Wrapper and provide the methods or action you want to perform on init and on dispose.
Code available on Github
NOTE: You can always add or remove method from Stateful Wrapper Class according to your need!!
Happy Fluttering!!

Trying to access state from widget, probably using wrong design

I'm learning flutter and trying to make a kind of MutableImage widget. The idea is to make a MutableImage StatefulWidget that would rebuild when a new image is provided. I try to avoid rebuilding the whole widget tree each time the image is changed because that seems overkill, and I plan to update the image several times per second. So I want to rebuild only that MutableImage widget.
So here is the code I have, with comments to explain where I'm stuck :
class MutableImage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MutableImageState();
}
void updateImage(List<int> bytes) {
// !!!!!! Would like to call this method here, but state is not available from Widget, which means I want to do something wrong, but not sure exactly how I should do it...
// this.state.updateImage(bytes);
}
}
class MutableImageState extends State<MutableImage> {
List<int> _bytes;
void updateImage(List<int> bytes) {
setState(() {
_bytes=bytes;
});
}
#override
Widget build(BuildContext context) {
if ((_bytes==null)||(_bytes.length==0)) {
return Center(child: CircularProgressIndicator());
}
return Image.memory(_bytes);
}
}
Then the idea was to use this widget like this for example in another stateful widget
MutableImage _mutableImage;
#override
Widget build(BuildContext context) {
if (_mutableImage == null) _mutableImage=MutableImage();
return : Row( //using Row as an example, the idea is that the mutable Image is deep into a tree of widgets, and I want to rebuild only _mutableImage when image changes, not all widgets.
children : <Widget>[
child0, child1, _mutableImage, child3, child4
]
);
}
void updateImage(List<int> bytes) {
_mutableImage?.updateImage(bytes);
}
So is there a good way to do this ? I'm quite confused, thx for any help/hint.
This is a place for an application of a GlobalKey. In the parent Widget of MutableImage make a global key and pass that to MutableImage. With that key you can access MutableImage state by using .currentState on the key and calling updateImage.
You'll have to add key as an argument of the MutableImage constructor and call super(key: key). updateImage should also be moved the the state of MutableImage.
Key:
final GlobalKey<MutableImageState> _imageKey = GlobalKey<MutableImageState>();
Pass the key:
MutableImage(key: _imageKey);
Access the state:
_imageKey.currentState.updateImage();

Flutter: A variable passed to the next screen becomes null

I want to pass a variable to the next screen but it becomes null in the next screen. what am I wrong with this? My code is like below.
first_screen.dart
onTap: () {
print(doc); // Prints out Instance of `DocumentSnapshot` on log
Navigator.of(context).pushNamed('/second', arguments: doc);
},
second_screen.dart
class SecondScreen extends StatefulWidget {
final DocumentSnapshot doc;
SecondScreen({
this.doc,
});
#override
State<StatefulWidget> createState() {
return _SecondScreenStateState();
}
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
super.initState();
print(widget.doc); // Prints out null here
}
I tried with othe data types but all variables become null in the next screen.
You have to pass argument like this:
Navigator.of(context).pushNamed('/second', arguments: doc);
for you is true but, use the ModalRoute.of() method to returns the current route with the arguments like this:
final doc = ModalRoute.of(context).settings.arguments as String;
I assumed that doc is String.
If you're sharing data between widgets (screens) try looking at InheritedWidget.Take a look at https://www.youtube.com/watch?v=1t-8rBCGBYw. You can also look at state management packages like provider which is pretty easy to understand or a bloc. These will save you in the long run.