Passing data to a previous screen - flutter

Firstly, if you pop a screen off, does it mean all initializations have been destroyed?
I initialized a few variables to a previous screen, was hoping to see the changes when I go back to the screen but I don't see the changes
Is it possible to pass a few data to a previous screen without moving to that screen? if yes, how?

Yes, it is possible.
You could do it in some variety of ways.
A simple way could be like that:
Having a private variable at the beginning of the method scope in a StatefulWidget
String _fileAvatarUpload = '';
Then, you would have your widget tree being displayed and a Widget you want to return a value to the tree:
Center(
child: ImagePickerSource(
isRunningWeb: kIsWeb,
image: _peopleModel.imageAvatar,
callback: callbackAvatar,
isAvatar: true,
imageQuality: 35,
),
),
In the callback method, you could use SetState to set the new value variable:
callbackAvatar(file, bytes) {
setState(() {
_fileAvatarUpload = file;
_bytesImgWeb = bytes;
});
}

Related

Return data when a screen is closed

I have several sub-screens which give the user the option to save some data. When that screen is closed, I want the parent screen, which pushed the sub-screen, to know whether data was saved or not. The sub-screens maintain a didSave flag and are set to true when data is saved.
There are several ways for the sub-screens to be closed:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
I can handle the 3rd case using Navigator.pop(context, didSave) and in the parent that didSave flag is captured using final didSave = await Navigator.push<bool>(context, myRoute).
However, for the first 2 cases, the result will obviously be null.
I have looked into WillPopScope but that only is used to determine whether the screen should be closed or not. It doesn't seem that I can set any data to be returned for the push call.
I have also looked into wrapping the parent screen in a Provider where it can listen to didSave states but that will trigger immediately when emitted which is not desirable for my use case. I only want to act when the sub-screen is closed not when data is saved.
I can potentially use WillPopScope and emit an event there if a save operation has occurred but I would like a simpler solution if available.
Is there a way for this that I am missing?
Thanks!
as you said the Provider will listen to didSave and this doesn't fit in your case you can use just a simple inheritedWidget:
wrapping the parent like this:
InheritedExampleWidget(
didSave: false,
child: Parent(),
),
you need to set a setter to didSave
then on the ascendant widgets on the widget tree, you can:
InheritedExampleWidget.of(context).didSave = true;
this will not trigger it immediately, which the Provider package solves.
then you can manage the state however you want
Did you try to create the variable with state management? And there is method with the push that do task after user come from the child screen. So, when they come you can checkout the use have saved data or not.
For EG:
saved = false; //State management variable
//We are pushing on another screen.
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) =>
new ScreenName(),
),
).then((val) {
//This part with work when user popped the screen on which we have pushed
if (!saved) {//Do Something when not saved}
});
Try above code and let me know if you get any error or you're facing any issue.
when you push a new route, using StatefulWidget, it will have a lifecycle starting from an createState, when the widget isn't there on the widget tree ( when we pop ), the dispose method will be called.
those cases of popping the route:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
will trigger the dispose method to execute.
so you can put inside it your logic that you want.
exmaple :
class classTest {
bool didSave = false;
}
then when on the property where you want to push the screen set it to that classTest's didSave, as an example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const YourRouteWidget(didSave: classTest.didSave,
),
),
);
on that point it's false, then when the user will complete using the screen, going back with any king of popping the route (with Navigator.pop(context), or with back button...), the dispose method will be called, so you can :
void dispose() {
if(/* your conditions*/ ) {
classTest.didSave = true;
}
super.dispose();
}
it will be back with a true value to the parent page.

Custom Event listeners in flutter

I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list.  All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?  
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}

How can I listen to a String variable change and perform an action when there is a change?

Is it possible to execute a function, lets call it myFunction() if a variable _myString is changed, but have this happen on the fly?
What I have is a textfield with a controller
var _myString;
const TextField(
controller:_controller
)
Now elsewhere in my widget tree, I have a button that can change the value of _myString, in this case I'm changing '_myString' to 'Testing'
GestureDetector(
onTap:(){ _myString = 'Testing'; }
child: Text('Testing')
)
Now what I'm hoping to achieve is that when the value of _myString changes in any way, I can perform some action of my choosing. In this case, I want to edit the TextField using the _controller, but I don't only want to do that, but a few other things, so I think its better to listen to changes in that variable and then execute a function
void myFunction(){
///Do some stuff
}
I'm using riverpod for state management in my app, and I was thinking I could try to use it, but have no idea how to use it to watch a single variable, I'm more familiar with using it for entire widgets. Alternatively using riverpod for something like this might be overkill.
I just don't know how to approach this problem, so any guidance would be really appreciated!
I believe you could use a ValueNotifier for this, a value notifier is a class that holds some value and "notifies" its listeners when this value changes. It is a simpler version of ChangeNotifier:
ValueNotifier<String> _myString = ValueNotifier<String>('');
With the above, whenever you want to read or write the value, use the value getter/setter:
print(_myString.value);
_myString.value = 'some value';
Now, to listen to changes you should use the addListener method:
#override
initState() {
// update _controller with value whenever _myString changes
_myString.addListener(() => _controller.text = _myString.value);
// print value on change
_myString.addListener(() => print(_myString.value));
// do stuff
_myString.addListener(myFunction)
}

Flutter HTTP Request Called Multiple Times On App Load [duplicate]

This question already has an answer here:
Flutter: Why setState(( ) { }) set data again and again
(1 answer)
Closed 2 years ago.
I have a flutter project that makes an http request to gather json of inventory items then render it on screen in a list view. When a user scrolls to the bottom it then loads more inventory by triggering another http call. However my initial HTTP call is being called multiple times. And I am not sure how to handle that.
The result I get is my finish print statement just triggers continuously. At first I thought I had it working because it does load the inventory to my list view. But then I noticed It just keeps loading the same data into my list view non stop. When I added that finish print statement it became clear that it is actually continuously making that http call non stop.
Id like to have it only make the call once, then make a new call again when the user scrolls to bottom.
Any tips will help thank you.
Here is my code.
Network Code
Future <List <dynamic>> fetchInventory() async{
dynamic response = await http.get('https:somelink.com',
headers: {'x-api-key': 'mykey'},);
var inventory = List<dynamic>();
//if 200 response is given then set inventory var to inventoryJson value from the http call
if (response.statusCode == 200){
dynamic inventoryJson = jsonDecode(response.body);
inventory = inventoryJson['page'];
print('finish');
}
//Inventory is returned
return inventory;
}
}
Here is my how I am using that code in my main file
class _WelcomeScreenState extends State<WelcomeScreen> {
//Create Network Helper Obj
NetworkHelper networkHelper = NetworkHelper();
//invenrtory List is set to empty
var inventory = [];
var _controller = ScrollController();
#override
void initState() {
super.initState();
// set up listener here
_controller.addListener(() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
// Perform your task
//This method fetches the data from the fetchInventory method
print('at bottom');
networkHelper.fetchInventory().then((value) {
//This set state will set the value returned from fetchInventory method to the local inventory List
setState(() {
this.inventory = value;
});
});
}
});
}
#override
Widget build(BuildContext context) {
//Used to format currency shown in view
var currencyFormat = NumberFormat('##,###.00', 'en_US');
//This method fetches the data from the fetchInventory method
networkHelper.fetchInventory().then((value){
//This set state will set the value returned from fetchInventory method to the local inventory List
setState(() {
this.inventory.addAll(value);
});
});
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0.0,
title: AppBarTitle(),
),
body: Padding(
padding: const EdgeInsets.only(left: 5.0, right: 5.0, bottom: 25.0),
child: ListView.builder(
controller: _controller,
itemCount: inventory.length,
itemBuilder: (context, index){
enter code here
enter code here
The responsibility of the build method is to construct a widget tree, and this method may be called repeatedly by the framework whenever it thinks that the screen might have changed and need to be re-rendered. As a result, its important that as little work as possible is done here. Remote calls over the network should be avoided, and implementers should try not to call setState as this will cause the UI rendering to loop.
Listening to the scroll controller as demonstrated is a good way of 'Loading more', what is missing is an initial load of the inventory in the initState() method.
So, you should move the call to networkHelper.fetchInventory() from the build method to the scrollcontroller callback (as it adds to the inventory) and move the existing call to networkHelper.fetchInventory() that initialises the inventory from the scrollcontroller callback to the start of the initState method.

How to delete data from StreamBuilder after reading?

I want to delete data from my stream after I read it.
Basically I want the same system than channel in Go.
So, if I add 5, 3 and 2, my stream contains 5, 3 and 2.
When I start reading, I get 5, and my stream now contains 3 and 2 etc...
Is it possible?
EDIT: Here my problem with some code.
I use a StreamBuilder to receive data. When I change the state, it trigger again my function like if I'd just receive data.
child: StreamBuilder<Tag>(
stream: widget.tagStream,
initialData: Tag(),
builder: (BuildContext context, AsyncSnapshot<Tag> snapshot) {
/// This should be trigger only when I receive data
if (mapController.ready && snapshot.hasData) {
tag = snapshot.data;
mapController.move(
LatLng(tag.position.latitude, tag.position.longitude),
mapController.zoom);
}
return RubberBottomSheet(...);
),
Here some context:
I have a map with icons representing objects. When I click on an icon or if I search the item related on my search bar, a RubberBottomSheet appears to show informations about the object. To do that, I use a StreamBuilder, so I just need to put the object clicked or searched in it to make my rubber appears and fill in. I also need to centrer on my icon to let the user know where is the object. My problem is that when I open or close my keyboard or when I use a setState (for changing the appearance of my search bar for example), it automatically trigger the StreamBuilder like if it receive new data.
Sorry, I should have started here...
The answer of Amine seems to be the best, but I'd like to share my solution too, maybe it'll help some persons.
After I've executed my code, I pass an empty object to my Stream. I just have to verify that my object is not empty before executing my code and everything works like a charm.
builder: (BuildContext context, AsyncSnapshot<Tag> snapshot) {
if (mapController.ready &&
snapshot.hasData &&
snapshot.data.mobile.nid != 0) {
tag = snapshot.data;
... /// My code
widget.tagStream.sink.add(Tag());
}
I've had similar behavior with a StreamBuilder and I couldn't find a solution for days. What I did instead is use a ListView builder that takes data from an InheritedWidget instead.
So basically instead of having to put data into the stream sink, I simply wrap my data setter in the InheritedWidget in a setState() and the ListView rebuilds every time I change the data.
N.B: My StreamBuilder also involved a map, I've always thought it was the one interfering with it but I never got to solve the problem. As in your case, every time I change the state, the stream rebuilds with the same data it had before.
Have similar problem in my app.
Workaroung I found is quite simple - global variable, which by default will do nothing ("false" in this example).
But in method which need to call setState() change this variable value to block next snapshot.data when rebuilding widget (in this example value "true" will block).
Remember to change variable to default value or you won't get new stream updates.
// global variable - doing nothing by default
bool _clear = false;
(...)
/// Add additional condition
if (mapController.ready && snapshot.hasData && !_clear) {
tag = snapshot.data;
mapController.move(
LatLng(tag.position.latitude, tag.position.longitude),
mapController.zoom);
}
/// change variable to default value
_clear = false;
return RubberBottomSheet(...);
),
(...)
// some method calling setState
void _clearMethod() {
_clear = true;
setState(() {});
}