Why can't I set a dynamic property inside a nested function? - flutter

I'm trying to create a function that can dynamically set the properties on an object like so:
void main() {
final obj = Item();
obj.update(5);
print(obj.xVal);
}
class ObjectBase {
void _setData(current, newValue) {
current = newValue;
print(current);
}
}
class Item extends ObjectBase {
int _x;
int get xVal => _x;
update(x) {
_setData(_x, x);
}
}
The print statement in _setData works fine, but it doesn't actually appear to change _x, even if it has been passed through. I expected that changing the reference here would update it everywhere.
So why isn't this working and is there a fix?
You can assume that I do have good reason to be calling _setData inside update rather than just implementing the functionality in update.
Update:
A real life example of what i'm trying to achieve
class ViewModel extends ChangeNotifier {
void _setDataFromDependency(current, newValue) {
if (!deepDynamicEquality(current, newValue)) {
current = newValue;
notifyListeners();
}
}
}
class ListScreenViewModel extends ViewModel {
int _localCount = 0;
List<int> _globalList;
ListScreenViewModel();
List<int> get globalList => _globalList;
int get localCount => _localCount;
incrementLocal() {
_localCount++;
notifyListeners();
}
void update(ListStore listStore) {
_setDataFromDependency(_globalList, listStore.globalList);
// if (!deepDynamicEquality(_globalList, listStore.globalList)) {
// _globalList = listStore.globalList;
// notifyListeners();
// }
}
}

An oversimplified workaround is to return the value from _setData . #julemand101 has already answered limitations.
class ObjectBase {
int _setData(current, newValue) {
current = newValue;
print('current: $current');
return current;
}
}
class Item extends ObjectBase {
int _x;
int get xVal => _x;
update(x) {
_x = _setData(_x, x);
}
}

Related

How to invoke a listener from another listener?

I'm using in_app_purchase package and I need to convert/map listener which is listening for List<PurchaseDetails> to another listener as shown below:
class Foo {
Foo() {
InAppPurchase.instance.purchaseStream.listen(_listener);
}
void _listener(List<PurchaseDetails> list) {
// How to pass these ids to `addListener()`
final List<String> ids = list.map((e) => e.productID).toList();
}
void addListener(void Function(List<String>) f) {}
}
This is how I want to use my listener
void main() {
Foo().addListener((List<String> ids) {});
}
Despite what your code comment says, I think what you're really asking for is for the internal _listener to invoke the callback that was previously passed as an argument to addListener (and not for _listener to call addListener directly, which it could just do directly).
Just have addListener save the callback to a member variable and let your internal listener invoke that:
class Foo {
void Function(List<String>)? _listener;
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
var listener = _listener;
if (listener == null) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
listener(ids);
}
void addListener(void Function(List<String>) f) => _listener = f;
}
If you want callers to be able to call addListener multiple times to register multiple callbacks, you would need to store them in a collection (and provide a mechanism to unregister callbacks):
class Foo {
final _listenerMap = <Object, void Function(List<String>)>{};
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
if (_listenerMap.isEmpty) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
for (var listener in _listenerMap.values) {
listener(ids);
}
}
Object addListener(void Function(List<String>) f) {
var token = Object();
_listenerMap[token] = f;
return token;
}
void removeListener(Object token) {
_listenerMap.remove(token);
}
}

Cannot convert Datatypes from-to

I have a class called DisplayInventory
Dictionary<InventoryObject, GameObject> itemsDisplayed = new
Dictionary<InventoryObject, GameObject>();
itemsDisplayed.Add(inventory.Container[i], obj);
the code breaks at this line (inventory.Container[i]) because it
cannot convert (field) List InventoryObject.Container.
this is my InventoryObject class
public class InventoryObject : ScriptableObject {
public List Container = new List();
public void AddItem(ItemObjectData _item, int _amount)
{
bool hasItem = false;
for (int i = 0; i < Container.Count; i++)
{
if(Container[i].item == _item)
{
Container[i].AddAmount(_amount);
hasItem = true;
break;
}
}
if(!hasItem)
{
Container.Add(new InventorySlot(_item, _amount));
}
}
}
[System.Serializable] public class InventorySlot {
public ItemObjectData item;
public int amount;
public InventorySlot(ItemObjectData _item, int _amount)
{
item = _item;
amount = _amount;
}
public void AddAmount(int value)
{
amount += value;
} }
The part where you declare the inventory variable or specifically inventory.container is missing.
Or is this the container from the InventoryObject class?
You need the specific type InventoryObject for the Dictionary.
What you are giving into it is simply just a List object that you put inside the container.
If you are refering to the container from the InventoryObject class, it really is just a List() that you hold there.
In this case the Dictionary would only need inventory as input.
Or you could change from
public List Container = new List();
to this
public List<InventoryObject> Container = new List<InventoryObject>();
This would still make more sense if this container is outside of the InventoryObject and in an Inventory class or something.

How to copy a list into another in a StateNotifier, so it update with every change

The List EleccionSinSeleccionClase is just a list of a class who has a String on it.
class EleccionSinSeleccionClase {
String Eleccion;
}
The state List is another class:
class EleccionConSleccionClase {
String Eleccion;
bool selec;
}
The problem is that I want to copy the first into the state of the StateNotifier, this line break the code.
This is the line: state[i].Eleccion = ListaElecciones[i].Eleccion;
class EleccionesConSeleccionNotifier
extends StateNotifier<List<EleccionConSleccionClase>> {
final List<EleccionSinSeleccionClase> ListaElecciones;
EleccionesConSeleccionNotifier({required this.ListaElecciones}) : super([]);
void init(){
print(ListaElecciones.length.toString());
if(ListaElecciones.length != 0){
for (int i = 0; i < ListaElecciones.length; i++) {
state[i].Eleccion = ListaElecciones[i].Eleccion; ////HERE////
}
}
}
}
final eleccionConSleccionStateNotifierProvider = StateNotifierProvider<
EleccionesConSeleccionNotifier, List<EleccionConSleccionClase>>((ref) {
final eleccioneswatch =
ref.watch(eleccionesSinSeleccionStateNotifierProvider);
return EleccionesConSeleccionNotifier(ListaElecciones: eleccioneswatch)..init();
});
Maybe the problem is that you initialize state as an empty list super([]) and then you're trying to change a value in an index that doesn't exist (state[i] where the list is obviously empty and cannot access that position)
void init(){
print(ListaElecciones.length.toString());
if(ListaElecciones.length != 0){
/// you will need to check if both lists are the same lenght if you actually want to do this
/// without failling
/*for (int i = 0; i < ListaElecciones.length; i++) {
state[i].Eleccion = ListaElecciones[i].Eleccion; ////HERE////
}*/
/// if you only want to copy Eleccion parameter in a new class this would be the easiest way
state = ListaElecciones.map((cb) => EleccionConSleccionClase(Eleccion: cb.Eleccion)).toList();
}
}

How to get function body as string using build_runner and source_gen?

My goal is to make my unit tests easy to understand. Currently, they are hard to understand because they have so many nested functions.
I want to use build_runner to generate the code of the unit with all functions unwrapped.
So here is an example of my current test:
test.dart
import 'package:example_usage/src/unwrap.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class Cat {
String sound() => "Meow";
int walk() => 4;
}
class Dog {
final Cat cat;
Dog(this.cat);
String sayHi() {
return this.cat.sound();
}
int jump() {
return this.cat.walk();
}
}
class MockCat extends Mock implements Cat {}
void main() {
MockCat cat;
Dog dog;
#UnWrap()
void setupCatSoundStub() {
when(cat.sound()).thenReturn("Woof");
}
#UnWrap()
void setupCatWalkstub() {
when(cat.walk()).thenReturn(2);
}
#UnWrap()
void expectCatCalled() {
verify(cat.sound());
}
#UnWrap()
void testDogWoof() {
setupCatSoundStub();
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
expectCatCalled();
}
void expectCatWalked() {
verify(cat.walk());
}
group('Dog Cat Play', () {
setUp(() {
cat = MockCat();
});
test('Dog woof', () {
testDogWoof();
});
test('Dog woof then jump', () {
testDogWoof();
setupCatWalkstub();
final steps = dog.jump();
expect(steps, 2);
expectCatWalked();
});
});
}
I want to generate a code like this
_$test.dart
void _$main() {
MockCat cat;
Dog dog;
void expectCatWalked() {
verify(cat.walk());
}
group('Dog Cat Play', () {
setUp(() {
cat = MockCat();
});
test('Dog woof', () {
// testDogWoof();
// setupCatSoundStub();
when(cat.sound()).thenReturn("Woof");
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
// expectCatCalled();
verify(cat.sound());
});
test('Dog woof then jump', () {
// testDogWoof();
// setupCatSoundStub();
when(cat.sound()).thenReturn("Woof");
dog = Dog(cat);
final sound = dog.sayHi();
expect(sound, "Woof");
// expectCatCalled();
verify(cat.sound());
// setupCatWalkstub();
when(cat.walk()).thenReturn(2);
final steps = dog.jump();
expect(steps, 2);
expectCatWalked();
});
});
}
I found some tutorial online but I could find documentations about getting the function body into string ( some like JavaScript's Function.prototype.toString() method ) I am new to code generation so I tried to print all fields but I can't find anything like that.
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
class InfoGenerator extends Generator {
#override
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) {
var buffer = StringBuffer();
// library.allElements.forEach((element) {
// buffer.writeln(
// '// ${element.displayName} - ${element.source.fullName} - ${element.declaration}');
// });
library.allElements.whereType<TopLevelVariableElement>().forEach((element) {
buffer.writeln('/*');
buffer.writeln(element.toString());
buffer.writeln('*/');
buffer.writeln(
'// ${element.name} - ${element.kind.displayName} - ${element.declaration}');
});
return buffer.toString();
}
}
I am also new to annotations so I just made this up
/// What to do here ?
class UnWrap {
const UnWrap();
}
Is what I am trying to do even possible ?

Using List to store and find class element with random member variable

I want to store a class in a List to later retrieve the value. Now the problem I
m having is that one of the members is a random value. So when trying to retrieve the entry I can't find it, since I can't produce the 'key'.
I'm happy for suggestions on implementing this in a different way.
So this is the example class I want to store in a list:
class MyDevice{
int device;
int randomNumber;
void set(int device, int random){
this.device = device;
this.randomNumber = random;
}
}
class Handling{
final List<MyDevice> myDevicesList = new List<MyDevice>();
...
...
MyDevice dev = new MyDevice();
dev.set(device, random);
//Store entry into List
myDevicesList.add(dev);
...
...
Now I want to delete or possibly replace an entry, but I will have no way of
finding it since the random value is not known after storing above.
// Note that I have the device value supplied in a call-back, for the sake of this
// example let's just define a method for removing
void removeEntry(int device){
MyDevice dev = new MyDevice();
dev.set(device, );
myDevicesList.remove(dev);
}
How can I find / remove an entry that is a class and I only want to 'search' the list with one of the embers in the class that is stored?
Is the only solution to walk the list and make my own compare method to identify the entry by the 'device' member?
All you need is the removeWhere List method. You have to remove where MyDevice.device matches the argument, device.
Here's the code you requested for
void removeEntry(device) {
myDevicesList.removeWhere((element) => element.device == device);
}
Here's the full code.
import 'dart:math';
class MyDevice {
int device;
int randomNumber;
void setValue(int device, int random) {
this.device = device;
this.randomNumber = random;
}
}
class Handling {
final List<MyDevice> myDevicesList = new List<MyDevice>();
void addToList(device, random) {
MyDevice dev = new MyDevice();
dev.setValue(device, random);
myDevicesList.add(dev);
}
void removeEntry(device) {
myDevicesList.removeWhere((element) => element.device == device);
}
List<MyDevice> get list {
return myDevicesList;
}
}
void main() {
final handle = new Handling();
handle.addToList(2, Random().nextInt(5));
handle.addToList(18, Random().nextInt(4));
handle.addToList(12, Random().nextInt(9));
print("List before ${handle.list.map((element) {
return [element.device, element.randomNumber];
})}");
handle.removeEntry(18);
print("List after ${handle.list.map((element) {
return [element.device, element.randomNumber];
})}");
}
The output is
List before ([2, 1], [18, 0], [12, 7])
List after ([2, 1], [12, 7])
Here's the solution on dartpad
You don't know random value, so you have to manually compare and remove item from list.
Following code may help you.
In following example i added 1,2 and 5 and remove 2 after that you can see in print only 1 and 5.
class MyDevice {
int device;
int randomNumber;
void set(int device, int random) {
this.device = device;
this.randomNumber = random;
}
}
class Delet2 extends StatefulWidget {
#override
_Delet2State createState() => _Delet2State();
}
class _Delet2State extends State<Delet2> {
final List<MyDevice> myDevicesList = new List<MyDevice>();
#override
void initState() {
super.initState();
add(1);
add(2);
add(5);
delete(2);
for (int k = 0; k < myDevicesList.length; k++) {
print(myDevicesList[k].device);
}
}
add(int number) {
MyDevice dev = new MyDevice();
dev.set(number, Random().nextInt(1000));
myDevicesList.add(dev);
}
delete(int number) {
for (int i = 0; i < myDevicesList.length; i++) {
if (myDevicesList[i].device == number) {
myDevicesList.removeAt(i);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}