Create a stream watching a list? - flutter

I've got a global list like this:
var data = [1,2,3];
I will add more elements to it, and each time I do I want something else to be notified - I need a stream and listener. So can I make a stream and tell it to run sink.add anytime the list mutates?
var stream = Stream.fromIterable(data);
stream.listen((item) => print(item));
> 1
> 2
> 3
data.add(4);
> Uncaught Error: Concurrent modification during iteration
data.remove(3);
> Uncaught Error: Concurrent modification during iteration
Oh no. What can I do to make this work?

This might help:
import 'dart:async';
class ObservableList<T> {
final _list = <T>[];
final _itemAddedStreamController = StreamController<T>();
final _listStreamController = StreamController<List<T>>();
Stream get itemAddedStream => _itemAddedStreamController.stream;
Stream get listStream => _listStreamController.stream;
void add(T value) {
_list.add(value);
_itemAddedStreamController.add(value);
_listStreamController.add(_list);
}
void dispose() {
_listStreamController.close();
_itemAddedStreamController.close();
}
}
void main() {
final observableList = ObservableList<int>();
observableList.itemAddedStream.listen((value) => print(value));
observableList.add(1);
observableList.add(2);
observableList.add(3);
observableList.add(4);
}

Related

what is Get.create() in the Getx package and what it does

reading the Getx package documentation, I faced this method:
Get.create<ShoppingController>(() => ShoppingController());
and it says:
Get.create(()=>Controller()) will generate a new Controller each time you call Get.find(),
but, I don't seem to understand what this means and how it differs from the Get.put() and Get.lazyPut().
I found the answer for that question.
The big difference between
Get.create<ShoppingController>(() => ShoppingController());
And :
Get.put(ShoppingController());
Both of them are used in injecting dependencies in the a Flutter app, But Get.put<T>(T()) injects it just one time and whenever we call Get.find<T>() it looks up for that exact dependency and return it, so we can rememeber this:
Get.put<T>(()) inject a dependency and whatever time we call Get.find<T>() across the entire app, the same T is returned.
In the other side, Get.create<V>(() => V) also inject a dependency in a Flutter app, But every time we call Get.find<V>(), it doesn't return the same V, it creates a new instance of V, then return it, so we can rememeber this:
Get.create<V>(() => V) don't return the same instance, it creates a new one every time Get.find<V>() is called.
Get.put(T()) :
class ControllerOne extends GetxController {
int number = 10;
increment() {
number += 10;
}
}
final controllerOne = Get.put<ControllerOne>(ControllerOne());
final controllerOneFinder = Get.find<controllerOne>();
controllerOneFinder.increment();
final controllerOneSecondFinder = Get.find<controllerOne>();
print(controllerOneFinder.number); // 20
print(controllerOneSecondFinder.number); // 20
it stay the same.
Get.create(() =>T) :
class ControllerTwo extends GetxController {
int secondNumber = 10;
increment() {
secondNumber += 10;
}
}
final controllerTwo = Get.create<ControllerTwo>(() => (ControllerTwo());
final controllerTwoFinder = Get.find<ControllerTwo>();
controllerTwoFinder.increment();
final controllerTwoSecondFinder = Get.find<ControllerTwo>();
print(controllerTwoFinder.number); // 20
print(controllerTwoSecondFinder.number); // 10
Each one is different than the other. (controllerTwoSecondFinder == controllerTwoFinder) is false.

How to define a Stack in dartPad?

https://dartpad.dev/?
import 'package:starter/stack.dart';
void main() {
print (calculation("(2+2*6"));
}
calculation(expression){
var tokens = expression.split(" ");
Stack values = Stack();
Stack ops = Stack();
}
note that if I remove the import (first line) I get this error message "The function 'Stack' isn't defined."
and when I add the import the error message is "Unsupported import(s): (package:starter/stack.dart)".
Stack is not built in the dart. But we can make a generic class with its methods(get, push, pop) implementation using list.
class CustomStack<T> {
final _list = <T>[];
void push(T value) => _list.add(value);
T pop() => _list.removeLast();
T get top => _list.last;
bool get isEmpty => _list.isEmpty;
bool get isNotEmpty => _list.isNotEmpty;
#override
String toString() => _list.toString();
}
Just put the above class somewhere in the project lib directory.
And use it like.
CustomStack<String> plates = CustomStack();
//Add plates into the stack
plates.push("Plate1");
plates.push("Plate2");
plates.push("Plate3");
// delete the top plate
plates.pop();

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);
}
}

objects are not stored into objectbox (Flutter)

So Im trying to save an object(with only one String attribute) in the ObjectBox. The ObjectBox is called favoriteBox. With the onLongPress-function im trying to put the object in the box. In the initState-function im trying to put all the objects (more specific: the string attributes of each object) in to a nested list.
My problem now is that after calling the onLongPress-function and putting the object in the favoriteBox, the favoritBox is null according to the Debugger.
Im not sure where my mistake is.
class _GridViewJokesState extends State<GridViewJokes> {
late int seasonsListIndex;
List<List<String?>> seasonsList = seasons;
final toast = FToast();
final AudioPlayer audioPlayer = AudioPlayer();
late SeasonProvider seasonProvider;
Store? _store;
Box<FavoritesEntity>? favoritesBox;
#override
void initState() {
super.initState();
toast.init(context);
getApplicationDocumentsDirectory().then((dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + "/objectbox");
});
favoritesBox = _store?.box<FavoritesEntity>();
seasonsList[5] =
favoritesBox?.getAll().map((e) => e.quoteName).toList() ?? [];
}
void onLongPress(listTileIndex) {
if (seasonProvider.seasonPicked) {
seasonsList[5].add(seasonsList[seasonsListIndex][listTileIndex]);
favoritesBox?.put(FavoritesEntity(
quoteName: seasonsList[seasonsListIndex][listTileIndex]));
toast.showToast(
child: buildToast("Zu Favoriten hinzugefĆ¼gt"),
gravity: ToastGravity.BOTTOM);
}
import 'package:objectbox/objectbox.dart';
#Entity()
class FavoritesEntity {
FavoritesEntity({required this.quoteName});
int id = 0;
String? quoteName;
}
The issue is that the code does not await the Future that returns the application Documents directory before accessing store. So store will still be null and the following line that gets a box does nothing.
getApplicationDocumentsDirectory().then((dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + "/objectbox");
});
// store is still null here:
favoritesBox = _store?.box<FavoritesEntity>();
You can either move the box get code into the then() closure. Or, as we recommend, init Store into a global variable using a helper class in the main function. See our examples.

Dart Analysis Detects and Unused Field...what am I missing?

As you can see from my code sample, I'm using this variable. I also reference multiple times later in the class.
Flutter Warning - info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
ng is: info: The value of the field '_loadTimer' isn't used. (unused_field at [app] lib/models/knowledge_level/pb_cycle_permissions_collection.dart:12)
import 'dart:async';
import 'dart:collection';
import 'package:app/data/graphql/queries.dart';
import 'package:app/helpers/shared_logger.dart';
import 'package:flutter/cupertino.dart';
import '../command_permission.dart';
class PBCyclePermissionsCollection
with ListMixin<CommandPermission>, ChangeNotifier {
Timer? _loadTimer;
///
/// CONSTRUCTION AND INITIALIZATION
///
static final PBCyclePermissionsCollection _instance =
PBCyclePermissionsCollection._internal();
factory PBCyclePermissionsCollection() {
return _instance;
}
/// ACCESS SINGLETON VIA myPBCyclePermInstance = PBCyclePermissionsCollection()
PBCyclePermissionsCollection._internal() {
_loadTimer = Timer(_waitFirstLoad, _attemptLoad);
}
///
/// PRIVATE VARIABLES AND METHODS
///
static final Duration _waitFirstLoad = Duration(milliseconds: 500);
static final Duration _waitRetryLoad = Duration(seconds: 2);
static final int _maxAttempts = 4;
int _loadAttempts = 0;
bool _isReady = false;
bool _hasFailed = false;
/// Storage of CommandPermissions List once loaded
final List<CommandPermission> _list = [];
void _attemptLoad() async {
_loadAttempts++;
SharedLogger.I().d('_attemptLoad() current load attempt: ${_loadAttempts}');
try {
final results = await Queries.getCommandPermissions();
var data = results.data!['commandPermissions'];
var permissions = <CommandPermission>[];
for (var item in data) {
permissions.add(CommandPermission.fromJson(item));
}
/// Populated class with loaded objects.
_list.clear();
_list.addAll(permissions);
_isReady = true;
notifyListeners();
} catch (e) {
SharedLogger.I().e('Error loading PBCycle Permissions - ${e}');
_newAttempt();
}
}
void _newAttempt() {
SharedLogger.I().d(
'_newTry() _loadAttempts: ${_loadAttempts} _maxAttempts:${_maxAttempts} '
'creating new loadTimer for another try? : ${!(_loadAttempts >= _maxAttempts)}');
if (_loadAttempts >= _maxAttempts) {
_hasFailed = true;
notifyListeners();
// TODO: do we invalidate any existing data that may have been loaded before? Like if this load cycle is a refresh?
// If so, we should reset _isReady and _list;
return;
}
_loadTimer = Timer(_waitRetryLoad, _attemptLoad);
}
///
/// PUBLIC METHODS
///
bool get isLoaded {
return _isReady;
}
bool get hasFailed {
return _hasFailed;
}
#override
set length(int newLength) {
throw ('length cannot be changed externally');
}
#override
int get length {
return _list.length;
}
#override
CommandPermission operator [](int index) {
return _list[index];
}
#override
void operator []=(int index, CommandPermission value) {
throw ('Cannot modify list from outside');
}
}
Image of IDE with Code Sample and associated Dart Analysis Hints
You aren't actually using it, you're just setting the value multiple times
The answer from Andrew is correct, but a bit unclear since unsure what 'it' refers to. Here's another way to explain what the warning message means:
Notice that the message says you are not using the value. You are using the variable, but not its value. You are assigning the value. To read the value would be using it.
That said, the question is answered, but I think the question is somewhat vague by asking "what am i missing". What do you (OP) want to achieve? I assume it's to not see that warning anymore. And that is what brings me to this post. I have similar issue. I too have a class variable for a Timer and I get this same warning message. One does not need to read the value in order to use a timer but the analyzer doesn't know that. While writing this response I have discovered that you can a suppress warning. How about this:
// ignore: unused_field
Timer? _loadTimer;