Flutter - Update parent state from child - dart

I'm not able to update the parent state from a child. The child widget has a callback named 'onDragEnd' which is called once the drag and drop operation on a grid cell is finished. At this time the parent (the grid) should rebuild UI according to the items new sort, but nothing happens. If I set a breakpoint before the 'setState()' the 'elems' property contains the correct items with right sorting. Even if I replace the entire list, UI is not updating. Any suggestions? What am I missing?
class _CustomGridState extends State<CustomGrid> {
List<Pack> elems;
_CustomGridState(this.elems);
#override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
children: this
.elems
.map((pack) => PackFingernail(pack: pack,
onDragEnd: (Pack source, Pack target) {
var sourceIndex = this.elems.indexWhere((p) => p.id == source.id);
var targetIndex = this.elems.indexWhere((p) => p.id == target.id);
this.elems.removeAt(sourceIndex);
this.elems.insert(targetIndex, source);
setState(() {}); // Not working <-----
})
).toList()
);
}
}

Try to add a key for your PackFingernail
// constructor
PackFingernail(..., {Key key}): super(key:key)
// in gridview
.map((pack) => PackFingernail(pack: pack,
onDragEnd: (Pack source, Pack target) {
...
},
key: Key(pack.id.toString()))
).toList()
Key can help to rebuild the GridView

Related

DropDownButton selected item isn't changing in UI

The objects I want to add in my DropDownButton:
class Car {
int id;
String make;
Car(this.id, this.make);
static List<Car> getCars() {
var cars = new List<Car>();
cars.add(Car(1, "Ford"));
cars.add(Car(2, "Toyota"));
cars.add(Car(3, "BMW"));
return cars;
}
}
Constructing the DropDown (StatefulWidget State class):
class _MyHomePageState extends State<MyHomePage> {
Car _selectedCar;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: getDropDown()));
}
Widget getDropDown() {
var cars = Car.getCars();
this._selectedCar = cars.first; // Default to first value in list.
var items = cars.map((car) {
return new DropdownMenuItem<Car>(
value: car,
child: new Text(car.make),
);
}).toList();
return DropdownButton<Car>(
value: this._selectedCar,
onChanged: (Car car) {
setState(() {
this._selectedCar = car;
});
},
items: items);
}
}
DropDownButton Shows up correctly with first item selected, but when I select another item the UI never updates to show the new item as selected.
You need to initalize list just once, because there wont be a match for DropDownList value if you init new list on every draw.
Working example found here: Gist
Try initializing the _selectedCar variable in your initState() method instead of the getDropdown() method.
According to the code you posted, the _selectedCar variable gets reinitialized every time you call setState() since build() method is called.
Also you mentioned you are getting the below error when trying the solution in the first answer:
I/flutter ( 5072): 'package:flutter/src/material/dropdown.dart':
Failed assertion: line 560 pos 15: 'items == null || I/flutter (
5072): items.isEmpty || value == null ||
items.where((DropdownMenuItem item) => item.value == I/flutter (
5072): value).length == 1': is not true.
This is most likely because more than one items in your dropdown is getting the same value.
A possible fix can be using the id parameter of the Car object as the dropdown value instead of the entire object, since the id will be unique for each object. More detail about this error can be found here.

How to perserve toggle switch state when changing tabs for flutter?

I'm building three pages of alarms (stored in different lists) that I have displayed via a Scaffold and TabViewer. Each alarm is stored as a row with a toggle switch to enable it. The rows for each page are stored as a List. Despite making sure to use set state when changing values and even trying to assign unique keys nothing I do seems to preserve the state of the switches when I changed tabs.
This is my first time coding in Flutter/Dart or designing an app for mobile in general. As such I'm still learning about some basic features of this language.
I've tried adding keys to everything using Uniquekey() to generate up keys didn't work so I've removed them.
I've made sure all variable changes are inside set state functions.
I've tried to store the variable inside the immutable super class of AlarmToggle which is both ill-advised and doesn't work anyways.
I haven't tired using PageStorageKey as I'm not sure how they'd be implemented in my code but I feel this is likely the only solution.
class Alarms {
List<Widget> allAlarms = []; // Store all alarms for the object
buildAlarm(
GlobalKey<ScaffoldState>
pageKey,
[int hour,
int minute,
List<bool> alarmDaysOfWeek]) {
TimeOfDay alarmTime = TimeOfDay(hour: hour, minute: minute);
AlarmRow _newAlarm = new AlarmRow(UniqueKey(), alarmTime, alarmDaysOfWeek);
allAlarms.add(_newAlarm);
}
void removeAlarm(GlobalKey<ScaffoldState> pageKey) {allAlarms.removeLast();}}
class AlarmRow extends StatefulWidget {
final TimeOfDay _alarmTime;
final List<bool> _alarmDaysofWeek;
final UniqueKey key;
AlarmRow(this.key, this._alarmTime, this._alarmDaysofWeek);
AlarmRowState createState() => new AlarmRowState();
}
class AlarmRowState extends State<AlarmRow> {
bool _alarmIsActive;
AlarmRowState(){_alarmIsActive = _alarmIsActive ?? false;}
void toggleChanged(bool state) {this.setState(() {_alarmIsActive = state;});}
#override
Widget build(BuildContext context) {
return new Container(
child: Row(
children: <Widget>[
new AlarmIcon(_alarmIsActive),
new Column(
children: <Widget>[
new AlarmTime(widget._alarmTime),
new AlarmWeekly(widget._alarmDaysofWeek),
],
),
new AlarmToggle(
_alarmIsActive,
() => toggleChanged(!_alarmIsActive),
),
],
),
);
} // Build
} // Class
No matter what I seem to try the _alarmIsActive variable in AlarmRow() gets reset to null each time the tab is changed. I'm trying to preserve its state when changing pages.
The solution is as jdv stated to use AutomaticKeepAliveClientMixin, it's not hard to use but I figured I'd include the instruction that I found after searching here in case anyone searches and finds this and doesn't know automatically how to implement it like myself.
class AlarmRowState extends State<AlarmRow> with AutomaticKeepAliveClientMixin {
#override bool get wantKeepAlive => true;
It's implemented in the state class with any modifiable variables you want to preserve. The 'with' after the 'extends' adds a Mixin which is a sort of class inheritance. Finally, it requires you to set the 'wantKeepAlive' to true and it compiles and state is no longer lost while the widget is not being rendered.
Why a stateful widget loses state while it's not rendered is something I'm still searching for. But at least I have a solution.
I hope this will help you #Ender ! check for this code, you can create a Global shared preference and use it, as shown below:-
class AlarmRow extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _AlarmRowState();
}
class _AlarmRowState extends State<AlarmRow>{
bool alarmIsActive;
#override
void initState() {
alarmIsActive = Global.shared.alarmIsActive;
super.initState();
}
#override
Widget build(BuildContext context) {
....
.....
....
....
body: new Container(
child: Switch(
value: alarmIsActive,
onChanged: (bool isEnabled) {
setState(() {
alarmIsActive = isEnabled;
Global.shared.alarmIsActive = isEnabled;
isEnabled =!isEnabled;
});
},
.....
......
),
),
);
}
}
class Global{
static final shared =Global();
bool alarmIsActive = false;
}
Switch enabled and will maintain its state

How to rebuild widget in Flutter when a change occurs

Edit: I've edited the code below to feature the method that fetches the data along with the widgets that build the train estimates (replacing any API information along the way with "API_URL" and "API_STOP_ID"). I hope this even better helps us figure out the problem! I really appreciate any information anyone can give -- I've been working very hard on this project! Thank you all again!
Original post:
I have a ListView of ListTiles that each have a trailing widget which builds train arrival estimates in a new Text widget. These trailing widgets are updated every five seconds (proven by print statements). As a filler for when the app is fetching data from the train's API, it displays a "no data" Text widget which is built by _buildEstimatesNull().
However, the problem is that "no data" is still being shown even when the app has finished fetching data and _isLoading = false (proven by print statements). Still, even if that was solved, the train estimates would become quickly outdated, as the trailing widgets are updating every five seconds on their own but this would not be reflected in the actual app as the widgets were built on page load. Thus, I need a way to rebuild those trailing widgets whenever they fetch new information.
Is there a way to have Flutter automatically rebuild the ListTile's trailing widget every five seconds as well (or whenever _buildEstimatesS1 is updated / the internals of the trailing widget is updated)?
class ShuttleApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new ShuttleState();
}
}
class ShuttleState extends State<ShuttleApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new HomeState();
}
}
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
arrivalsList.clear();
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
_buildEstimateNull();
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
class ArrivalEstimates {
final String routeId;
final String arrivalAt;
final String stopId;
ArrivalEstimates(this.routeId, this.arrivalAt, this.stopId);
}
Thank you so much in advance for any help you can give! I really super appreciate it! :)
There are a few ways you could tackle this. It is slightly difficult however to tell what's going on without seeing a bit more of your code - specifically how you're getting the data and what you're doing with it. But I think I can give you a sufficient answer anyways.
The simple way of doing this is to either:
Have a StatefulWidget which keeps track of the build estimates for all of the items in the list. It should request data from your API, get the results, and then call setState(() => this.listData = data);. The call to setState is what tells the widget that it needs to rebuild.
Have a StatefulWidget for each item in the list. They would all each perform an API request every 5 seconds, get the results, and then each would call setState(() => this.itemData = data);. This means multiple calls to the API etc.
The advantage of #1 is that you can batch API calls, whereas the advantage to #2 is that your build would change less overall (although the way flutter works, this would be pretty minimal)... so I would probably go with #1 if possible.
However, there is a better way of doing this!
The better way of doing this is to have some sort of API Manager (or whatever you want to call it) which handles the communication with your API. It probably would live higher up in your widget tree and would be started/stopped with whatever logic you want. Depending on how far up the widget tree is, you could either pass it into each child or more likely hold it in an InheritedWidget which could then be used to retrieve it from each list element or from the overall list.
The API manager would provide various streams - either with a bunch of named fields/methods or with a getStream(id) sort of structure depending on your API.
Then, within your various list elements, you would use StreamBuilder widgets to build each of the elements based on the data - by using a StreamBuilder you get a ConnectionState object that lets you know whether the stream has received any data yet so you can choose to show an isLoading type widget instead of the one that shows data.
By using this more advanced method, you get:
Maintainability
If your API changes, you only have to change the API manager
You can write better testing as the API interactions and the UI interactions are separated
Extensibility
If you, later on, use push notifications for updates rather than pinging a server every 5 seconds, that can be incorporated into the API manager so that it can simply update the stream without touching the UI
EDIT: as per OP's comments, they have already implemented more or less the first suggestion. However, there are a few problems with the code. I'll list them below and I've posted the code with a couple of changes.
The arrivalsList should be replaced each time a new build is done rather than simply being changed. This is because dart compares the lists and if it finds the same list, it doesn't necessarily compare all of the elements. Also, while changing it in the middle of a function isn't necessarily going to cause problems, it's generally better to use a local variable and then change the value at the end. Note that the member is actually set within setState.
If serviceActive == false, the return was missed from return _buildEstimateNull();.
Here's the code:
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
var arrivalsList = new List<ArrivalEstimates>(); // *********** #1
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
print("no service id");
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
this.arrivalsList = arrivalsList; // *********** #1
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
return _buildEstimateNull(); // ************ #2
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
print("arrivalsList length: ${arrivalsList.length}");
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
print("Estimate match found: ${arrival.stopId}");
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
Instead of clearing and re-using the arrivalsList, create a new list every time the data is fetched. Otherwise Flutter is unable to detect if the list has changed.
Also, the code would clearer if you called setState whenever you change the list.
_fetchData() async {
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
globals.serviceActive = false;
setState(() {
_isLoading = false;
});
} else {
final newArrivalsList = new List<ArrivalEstimates>();
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
newArrivalsList.add(arrivalEstimate);
});
}
});
setState(() {
arrivalsList = newArrivalsList;
_isLoading = false;
});
}
}
A few side notes:
I'm not sure if you actually want to clear the list before you fetch the data. If the state was updated properly, that would cause a flicker every 5 seconds.
I'm not sure if you simplified the code, but calling the _fetchData method every five seconds may become a problem if the network is slow.
If you are certain that you want a child widget to rebuild every time you call setState() and it is stubbornly refusing, you can give it a UniqueKey(). This will ensure that when setState() triggers a rebuild the child widget keys will not match, the old widget will be popped and disposed of, and, the new widget will replace it in the widget tree.
Note that this is using keys in sort of the opposite way for which they were intended (to reduce rebuilding) but if something beyond your control is hindering necessary rebuilds then this is a simple, built-in way to achieve the desired goal.
Here is a very helpful Medium article on keys from one the Flutter team members, Emily Fortuna:
https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d
I am not sure if this is what your looking for but and im probably late on this but i believe you can use a change notifier efficiently to achieve this. Basically a change notifier is hooked to your backed logic() for instance an api data fetch. A widget is then registered with a change notifier of the same type as the change notifier provider. In event of data change, the widgets registered with the change notifier will be rebuild.
For instance
// extend the change notifier class
class DataClass extends ChangeNotifier {
....
getData(){
Response res = get('https://data/endpoint')
notifyListeners()
}
void onChange() {
notifyListeners();
}
....
}
Every time there is change in data you call the notifyListeners() that will trigger rebuild of consuming widgets.
Register you widget with a changenotifier
class View extends StatefulWidget {
Widget create(BuildContext context) {
return ChangeNotifierProvider<ModelClass>(
builder: (context) => DataClass(auth: auth),
child: Consumer<ModelClass>(
builder: (context, model, _) => View(model: model),
),
);
}
}
You can also user a Consumer for the same. Get more on this from the Documentation

How do I insert widgets at the top of a ListView?

Brief Note:
In all of my code examples you will see things like material.Widget instead of just Widget. This is because I like to name my imports like this for example:
import 'package:flutter/material.dart' as material;
My Question:
I am trying to make a ListView show newly added Widgets at the top of the list. I have a simple stateful widget that only contains a list of models (classes that contain necessary information to construct the widgets that are going into the ListView), and should construct ListView's children based off of that list.
class Page extends material.StatefulWidget {
final List<CardModel> cardModels;
Page(this.cardModels);
#override
_PageState createState() => new _PageState(cardModels);
}
class _PageState extends material.State<Page> {
List<CardModel> cardModels;
_PageState(this.cardModels);
#override
material.Widget build(material.BuildContext context) {
return new material.ListView(
children: cardModels.map((cardModel) => new Card(cardModel.cardID)).toList(),
);
}
}
The behavior that I expect from this is that whenever the build method is called (and setState is properly used), that the ListView should be reconstructed properly and contain child widgets in the order of the list. It successfully does this if I simply add new models to cardModels sequentially:
cardModels.add(new CardModel(nextID++));
You can see widgets get added sequentially with their id's incrementing properly:
However, I want newer widgets to be inserted at the top (which would be shown with higher ids at the top). In order to accomplish this, I try inserting new models at the beginning of the list:
cardModels.insert(0, new CardModel(nextID++));
Unfortunately, instead of seeing the correct widgets, I just get widget with id 0 over and over again:
I know that the list of models is being updated correctly because I can print it out and see the ids in descending order. I am assuming that there is something about how flutter detects changes to widgets that is causing this behavior, but after a lot of reading, I still have not been able to figure it out. Any help would be much appreciated. Also, I call set state in the widget that ends up building the page (the widget containing the ListView) as one of its children:
btn = new FloatingActionButton(
onPressed: () => setState(_pageController.addCard),
tooltip: 'Upload File',
child: new Icon(Icons.file_upload),
);
_pageController is the object that modifies the list of models. If this is not enough information, let me know and I am happy to provide more code or answer any questions.
There is a simple quick fix, using the didWidgetUpdate override,
by checking if the cardModels object in the oldWidget is the same in the cardModels being passed as a parameter.
like so
#override
void didUpdateWidget(covariant Page oldWidget) {
if (widget.cardModels != oldWidget.cardModels) {
setState((){
cardModels = widget.cardModels;
});
}
super.didUpdateWidget(oldWidget);
}
Full example below
class Page extends material.StatefulWidget {
final List<CardModel> cardModels;
Page(this.cardModels);
#override
_PageState createState() => new _PageState(cardModels);
}
class _PageState extends material.State<Page> {
List<CardModel> cardModels;
#override
void initState(){
cardModels = widget.cardModels;
super.initState();
}
#override
void didUpdateWidget(covariant Page oldWidget) {
if (widget.cardModels != oldWidget.cardModels) {
setState((){
cardModels = widget.cardModels;
});
}
super.didUpdateWidget(oldWidget);
}
_PageState(this.cardModels);
#override
material.Widget build(material.BuildContext context) {
return new material.ListView(
children: cardModels.map((cardModel) => new Card(cardModel.cardID)).toList(),
);
}
}
Turns out, the Card widget I made did not need to be stateful. I changed it to stateless and it solved the problem. No idea why having them as stateful would break it though.
On FloatingActionButton change the function to insert new item and then invoke setState like:
cardModels.insert(0, new CardModel(nextId++)) ;
setState(() {
cardModels = cardModels;
});
Hope that helped!

Flutter Dismissible insists list item must be removed from tree

I am using a list of Dismissible items and want a swipe in one direction to delete the item but a swipe in the other direction to initiate an edit of the item. However, Flutter insists that a Dismissible item must be removed from the tree in the onDismissed callback. I've tried re-inserting the item but that doesn't work. Any ideas? Extract from the code creating the list items is below:
return new Dismissible(
key: new ObjectKey(item),
direction: DismissDirection.horizontal,
onDismissed: (DismissDirection direction) {
setState(() {
item.deleteTsk();
});
if (direction == DismissDirection.endToStart){
//user swiped left to delete item
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text('You deleted: ${item.title}'),
action: new SnackBarAction(
label: 'UNDO',
onPressed: () { handleUndo(item); }
)
));
}
if (direction == DismissDirection.startToEnd){
//user swiped right to edit so undo the delete required by flutter
Async.scheduleMicrotask((){handleUndo(item);});
Navigator.of(context).pushNamed('/tskedit');
}
},
...
You can use confirmDismiss function of Dismissible widget for this purpose.
If you don't want the widget to get dismissed, then you just need to return false from confirmDismiss.
Don't use onDismissed to do your post-swipe processing, use confirmDismiss instead, it will provide you with the swipe direction just as onDismissed.
Here is the official documentation for confirmDismiss function:
Gives the app an opportunity to confirm or veto a pending dismissal.
If the returned Future completes true, then this widget will be
dismissed, otherwise it will be moved back to its original location.
If the returned Future completes to false or null the [onResize]
and here is an example:
Dismissible(
confirmDismiss: (direction) async {
if (direction == DismissDirection.startToEnd) {
/// edit item
return false;
} else if (direction == DismissDirection.endToStart) {
/// delete
return true;
}
},
key: Key(item.key),
child: Text(item.name),
)
The Dismissible will think your item was dismissed as long as the item key changes. Let's say your item class is MyItem. If you implement a constructor MyItem.from in your MyItem class that copies the fields over, e.g.:
class MyItem {
MyItem({ #required this.title, #required this.color });
MyItem.from(MyItem other) : title = other.title, color = other.color;
final String title;
final Color color;
}
Then you can replace handleUndo(item) with handleUndo(new MyItem.from(item)) so that your new ObjectKey(item) will be unique from the old ObjectKey that you used before (assuming you didn't implement operator == on MyItem).

Resources