Changes in Object from Second Screen is also Changing the Value in First Screen in flutter dart - flutter

A class Object passing from First Screen to the second Screen While on the second screen when the object changed its value, it's also changing the value on first screen.
Code of First Screen. (widget.templateModel is an object class that i am passing to the second screen)
Navigator.push(context,MaterialPageRoute(builder: (context) =>
EditEmojiTextTemplateScreen(templateModel: widget.templateModel,));
Code of Second Screen (On the second screen i am receiving the object and when i am changing the value of widget.templateModel it also changing the value on the first screen for a simple understandable code below i changed the value in initState while in the gif i am changing value in TextFormField)
class EditEmojiTextTemplateScreen extends StatefulWidget {
final TemplateModel templateModel;
EditEmojiTextTemplateScreen({
Key? key,
required this.templateModel,
}) : super(key: key);
#override
State<EditEmojiTextTemplateScreen> createState() =>
_EditEmojiTextTemplateScreenState();
}
class _EditEmojiTextTemplateScreenState
extends State<EditEmojiTextTemplateScreen> {
final SharedPreferences sharedPreferences = sl();
var txtNameController = TextEditingController();
var txtColorController = TextEditingController();
_EditEmojiTextTemplateScreenState();
#override
void initState() {
widget.templateModel.emoji[0].titleTwo = "kdfff"; //here i am changing the value and it also changing the value on first screen and i dont want this behavior of this object
super.initState();
}
Note: This is happening because of widget variable as mentioned in the documentation but i don't know how to prevent this behavior.
package:flutter/src/widgets/framework.dart
The current configuration.
A [State] object's configuration is the corresponding [StatefulWidget]
instance. This property is initialized by the framework before calling
[initState]. If the parent updates this location in the tree to a new
widget with the same [runtimeType] and [Widget.key] as the current
configuration, the framework will update this property to refer to the
new widget and then call [didUpdateWidget], passing the old
configuration as an argument.

Now I see what you are trying to do.
You could initialize a NEW istance of TemplateModel in the InitState of the second screen.
Then, set the new object's properties like this (or write a cleaner method to do that):
newObject.property1 = oldObject.property1;
newObject.property2 = oldObject.property2;
...
Once the user presses the save button, change oldObject's properties again, so that the first page updates.
You might want to take a look at state management to better understand how to approach this kind of problems.

As the other answer suggests, take a look at state management solutions.
Also keep the models immutable by creating them with final fields. Then to modify, create new instances via copyWith()

Please update you code after navigation.then method
template = snapshot.data;

Related

Is there a way to navigate to an specific child or row in flutter?

Is there a way to navigate from one dart "page" to a specific point in another? This will get me to a given page
Navigator.push(
context,
MaterialPageRoute(builder: (context) => WK3()),
);
But I want to navigate to a specific child or row within that page (which are unfortunately fairly long, and would otherwise require a lot of scrolling).
I am used to working with html, where you just have to indicate a position within a page using a hash tag:
#here
That should be possible to do in Flutter/Dart, right?
This is not possible by just using the flutter Navigator. What I would do to tackle that issue is that I would pass an argument which contains the scroll position to the Navigator for example:
Navigator.pushNamed(
context,
'/wk3',
arguments: {'scrollTo': elementId}, // or any other logic like half of the screen or so
);
To read more about Navigator and arguments you can check out the official documentation here. You can also do that for none named routes obviously.
Inside your target widget you could then do the following approach.
Take the argument and parse it to whatever you need.
Depending on your page and your scroll behavior you could use the initState to directly scroll to your desired location. What happens next is a bit dependend on your concrete implementation or where you want to scroll. In certain situations it might be more useful to add a postFrameCallBack for your scrolling instead of doing it in the initState. I'll add it for educational reasons in the snippet below.
Assuming we have a ScrollController of a ListView for example the widget we navigated to knows where we want it to scroll to due to our passed argument. If you use for instance a position value here and we have the ScrollController to do something like this:
controller.position.animateTo(
widget.args.scrollTo, //make sure it has the correct type
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
);
There are also ways you could scroll to a certain element in a list or a column (like for example the 100th element). Check this question for more information. You can find a slight implentation with a scroll controller below:
class ScreenArguments {
final String scrollTo;
ScreenArguments(this.scrollTo);
}
class Screen extends StatefulWidget {
final ScreenArguments args;
Screen(this.args, {Key key}) : super(key: key);
#override
ScreenState createState() => ScreenState();
}
class ScreenState extends State<Screen> {
#override
void initState() {
scrollMeTo = widget.args.scrollTo;
scrollController = ScrollController();
WidgetsBinding.instance
.addPostFrameCallback((_) => scrollTo(context)); // this is probably safer than doing scrollTo(context) directly in your initState
enter code here
// if you do not use addPostFrameCallback you can call scrollTo(context) directly.
//scrollTo could use scrollControler.animateTo() etc.
}
I dont have ScrollController / ListView implementation
If thats not the case and you do not have a ScrollController and you want just to scroll to any element on your widget things get a little bit more complicated. In that case I'd recommened you to use flutters Scrollable.ensureVisible. Taken from the documentation it does the following:
Scrolls the scrollables that enclose the given context so as to make
the given context visible.
Lets assume you have Column inside a SingleChildScrollView to have a foundation for your scrolling behavior. You would then define a GlobalKey for each section of your widget you would like to scroll to. This key would be the identifier which we pass in as an argument. Assuming we have a GlobalKey in the widget which is called second we could do the following:
Scrollable.ensureVisible(
GlobalObjectKey(widget.args.scrollTo).currentContext, //this would reference second
alignment: 0.5, //
duration: Duration(seconds: 2),
curve: Curves.easeInOut);
You can read more about Scrollable.ensureVisible here.
What approach to take is dependended on your needs and on your implementation.

Avoid no_logic_in_create_state warning when saving reference to State of StatefulWidget in Flutter

Using Flutter, I display a list of elements in an app.
I have a StatefulWidget (ObjectList) that holds a list of items in its State (ObjectListState).
The state has a method (_populateList) to update the list of items.
I want the list to be updated when a method (updateList) is called on the widget.
To achieve this, I save a reference (_state) to the state in the widget. The value is set in createState. Then the method on the state can be called from the widget itself.
class ObjectList extends StatefulWidget {
const ObjectList({super.key});
static late ObjectListState _state;
#override
State<ObjectList> createState() {
_state = ObjectListState();
return _state;
}
void updateList() {
_state._populateList();
}
}
class ObjectListState extends State<ObjectList> {
List<Object>? objects;
void _populateList() {
setState(() {
// objects = API().getObjects();
});
}
// ... return ListView in build
}
The problem is that this raises a no_logic_in_create_state warning. Since I'm not "passing data to State objects" I assume this is fine, but I would still like to avoid the warning.
Is there a way to do any of these?
Saving the reference to the state without violating no_logic_in_create_state.
Accessing the state from the widget, without saving the reference.
Calling the method of the state from the outside without going through the widget.
It make no sense to put the updateList() method in the widget. You will not be able to call it anyway. Widgets are part of a widget tree, and you do not store or use a reference to them (unlike in other frameworks, such as Qt).
To update information in a widget, use a StreamBuilder widget and create the widget to be updated in the build function, passing the updated list to as a parameter to the widget.
Then, you store the list inside the widget. Possibly this may then be implemented as a stateless widget.

GetX Observable works with list but not custom class

I am using GetX and Firebase Realtime Database. When I use this in my GetX controller, and Update a certain value (like the username), the Obx widget in my main screen is updated. Everything works smoothly.
Rx<List<FrediUser>> frediUser = Rx<List<FrediUser>>([]);
String get username => frediUser.value.first.username;
#override
void onInit() async {
super.onInit();
User? user = Get.find<AuthController>().user;
String uid = user!.uid;
frediUser.bindStream(DatabaseManager().frediUserStream(uid));
ever(frediUser, everCalled);
}
But I only have one current user which I want to fetch, so making it a list is unnecesary. If I take the list off like so:
Rx<FrediUser?> frediUser = Rx<FrediUser?>(null); it stops working.
Of course I modify the subsequent lines to adjust for this. Or if I am using a custom class it doesn't notice the update.
Note: The stream is called (checked with a print statement), but it is as if the update never gets to the controller or the UI.
It seems as if the value is nullable, the ever function is not called.
Related question: here
Change it to the following:
Rxn<FrediUser> frediUser = Rxn<FrediUser>();
String get username => frediUser.value.first.username;

What is the difference between the following var initialization positions in a StatefulWidget?

I was wondering about where to declare and initialize variables in the case of a StatefulWidget. There seem to be a couple of ways to do it, but are there differences, any guidelines, or best practice approaches for it?
I created the below sample, but can not find any differences except that when performing a hot reload, variable i loses its value and is back to zero again.
I read this, but it contains so many contradicting comments.
class Sample extends StatefulWidget {
int i=0;
late Object object1 = Get.put(Object());
#override
_SampleState createState() => _SampleState();
}
class _SampleState extends State<Sample> {
int j = 0;
late Object object2;
#override
void initState() {
i=5;
j=5;
object1.param="value123";
object2=Get.put(Object());
object2.param="value123";
}
#override
Widget build(BuildContext context) {
}
}
First, if you run your app on the emulator you can indeed find no differences. But, that observation is huge misleading!
Variables declared whether initialized or not inside the Widget class are not persisted in the case of widget recreation. StatefulWidgets (and all Widget subclasses) are thrown away and rebuilt whenever configuration changes. Luckily, you can force performing Widget recreation by performing a hot reload on the widget while you are testing your app. to ensure proper behavior.
If you want to declare variables that should persist (State data), make sure to put them inside the State class as int j in the above code example.
Use initState() for variables that you cannot initialize within the declaration statement.
Why then object1 retain its data?
Simply, because GetX will not recreate a new instance of Object if one already exists. It will return back the old instance every time the Widget is rebuilt. That's why no difference is perceived in the case of object1 and object2 places of declaration.
At the time of writing this answer, you have to manually call Get.delete<>() in order to dispose a controller if you are not using bindings.

Can a StatelessWidget contain member variables?

I have a StatelessWidget that uses a ScopedModel to store its data. The widget basically presents a list of checkboxes and some buttons to save the state of the checked boxes.
Now I want to keep track of if the user has altered any of the checkboxes, i.e. checked/unchecked them since the widget was displayed. So I added something like this:
class MyCheckboxScreen extends StatelessWidget{
bool _hasBeenModified = false;
void _itemCheckedChange(bool checked, MyModel model){
_hasBeenModified = true;
// Code to change the model here
}
void _onCloseButton(){
if( _hasBeenModified ){
// Show a warning that there are modifications that will not be be saved
}
}
void _onSaveButton(Context context, MyModel model){
model.save();
Navigator.of(context).pop();
}
}
This seems to work, but my StatelessWidget now contains its own state.
The state isn't used to update the UI and redraw anything, it's only used to check if there are modifications to any checkbox when pressing the "Close" button.
Is it safe for a StatelessWidget to have this kind of internal state? Or could it be a problem? For example, could the widget be recreated unexpectedly, and the internal state lost?
I don't find the documentation to be very clear on this, it just says
For compositions that can change dynamically, e.g. due to having an internal clock-driven state, or depending on some system state, consider using StatefulWidget.
But this sounds like it only applies to state that affects the UI and requires rebuilding the widget.
Yes, a StatelessWidget can have mutable variables (your code compiles) but that's wrong.
A widget that does not require mutable state
This is taken from the documentation. Even if you have a single non-final variable, it means that something can actually be changed in your widget. It's not an immutable class. Ideally, you should use StatelessWidgets like this:
class MyWidget extends StatelessWidget {
final int a;
final String b;
const MyWidget(this.a, this.b);
}
Or something similar such as
class MyWidget extends StatelessWidget {
const MyWidget();
}
When you have non final variables, use a StatefulWidget. Your class has to clearly be a StatefulWidget:
// This is not final. It can be changed (and you will change it)
// so using the stateless way is wrong
bool _hasBeenModified = false;
void _itemCheckedChange(bool checked, MyModel model){
_hasBeenModified = true;
}
The UI doesn't matter actually because here's a matter of variables and mutability. Something is changing (bool _hasBeenModified) so it cannot be a StatelessWidget because it has to be used in all those cases where the state is immutable.
Are there any reasons why you don't use a stateful widget? Stateless widgets aren't intended to be used that way.. And without any benefits, I don’t understand why you overcomplicate things..