GetX Unbind Stream - flutter

I am using the bindStream() function with the GetX package inside a controller.
class FrediUserController extends GetxController {
#override
void onReady() {
super.onReady();
final userController = Get.find<FrediUserController>();
var groupIds = userController.user.groups;
groupList.bindStream(DatabaseManager().groupsStream(groupIds));
ever(groupList, everCallback);
}
}
But, when the groupIds update in the FrediUserController (with an ever function that gets triggered, I want to RE-bind the streams. Meaning, delete the existing ones and bind again with new ids, or replace the ones that have changed.
Temporary Solution: Inside ever() function
Get.delete<FrediGroupController>();
Get.put(FrediGroupController());
This code gets run everytime my groupIds change from the database. But I do not want to initiate my controllers every time a small thing changes, it is bad UX.
This seems difficult, could someone guide me to the right direction? Maybe there is a completely different approach to connecting two GetX controllers?

Note: the first one include editing the source code of the Getx package.
first:
looking in the source code of the package :
void bindStream(Stream<T> stream) {
final listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
here is what the bind stream actually do, so if we want to access the listSubscriptions list, I would do:
final listSubscriptions;
void bindStream(Stream<T> stream) {
listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
now from your controller you will be able to cancel the streamSubscription stored in that list with the cancel method like this :
listSubscriptions[hereIndexOfThatSubscription].cancel();
then you can re-register it again with another bindStream call
second :
I believe also I've seen a method called close() for the Rx<T> that close the subscriptions put on it, but I don't know if it will help or not
Rx<String> text = ''.obs;
text.close();

I've also run into this issue, and there appears to be no exposed close function. There is a different way to do it though, using rxdart:
import 'package:get/get.dart';
import 'package:rxdart/rxdart.dart' hide Rx;
class YourController extends GetxController {
final value = 0.obs;
final _closed = false.obs;
void bindValue(Stream<int> valueStream) {
_closed
..value = true
..value = false;
final hasClosed = _closed.stream.where((c) => c).take(1);
value.bindStream(
valueStream.takeUntil(hasClosed)
);
}
}
Whenever you want to unbind, just set _closed.value = true.

Related

How to create a method inside a provider class

I want to be clear and precise. I have a database with 260 variables, I have a data model class and a SQLiteHelper class. I'm using a provider and I have all the CRUD inside.
The problem comes because I have scrolleable page in which I want to be able to change all variables. Around 240 variables are int? and each one will have a button which do the same, convert it to zero if it is null or add 1 if it is an integer. I'm not using a normal callback because in that scrolleable page I use different reusable buttons and I want to know the value of the variable in child and parent widget. In the reusable buttons for change color and text, and in the parent widget(scrolleable page) to save them in SQlite at the end of the page with a save button.
This is my provider class
class DBProvider with ChangeNotifier {
final SQLiteHelper0 _db = SQLiteHelper0();
List<DB> _items = [];
Future<void> loadDB() async {
List<Map<String, dynamic>> data = await _db.charDB;
_items = data.map((DB) {
return DB(
id: charDB["id"],
name: charDB["name"],...// the rest of the CRUD
I'm trying something like that
dynamic increment(index){
if(_items[index] != int?){
return _items[index];
} else if (_items[index]! == null){
return _items[index]== 0;
}else { return _items[index] + 1;}
}
an then in the scrolleable page make another function like that
late DBProvider _dBProvier;
void _increment(index){setState(() {
_dBProvider.increment(index);
});}
I am having different problems, at times I think that nothing makes sense. But if it is a totally dumb way, please give me some direction to keep trying. This question is related with other question where I focused the problem in a different and wrong way Why this function is null?
Thanks.

dart - How to listen for Text-Change in Quill text Editor (Flutter)

I am only familiar with HTML/CSS/JS, and basics of dart/flutter
Developer Level: Beginner
Project type & language: I am developing a notes app for myself, using flutter.
My aim is to save my note, as soon as I update the text... for which I need to use a dart Function to run on every 'text-change' event..
How do I use the Text-Changes event of Quill Editor to detect changes in the Content
THE EQUIVALENT OF THIS IN JAVASCRIPT IS GIVEN BELOW , BUT I DON'T KNOW HOW TO DO IT DART & FLUTTER.
quill.on('text-change', function(delta, oldDelta, source) {
if (source == 'api') {
console.log("An API call triggered this change.");
} else if (source == 'user') {
console.log("A user action triggered this change.");
}
});
You can listen to quill document changes stream and handle it accordingly.
_controller.document.changes.listen((event) {
print(event.item1); //Delta
print(event.item2); //Delta
print(event.item3); //ChangeSource
});
I am also run into this issue. After some hours of research, I found a solution. You habe to add a listener to your QuillController, which will be called on each editor event, like pressed keys or toolbar actions.
Use the initState() method in your State class for adding a listener.
class _TextEditorState extends State<TextEditor> {
final QuillController _controller = QuillController.basic();
#override
void initState() {
_controller.addListener(() {
print('Here I am, rock me like a hurricane!!!');
});
super.initState();
}
#override
Widget build(BuildContext context) {
// Build your Widget with QuillToolbar and QuillEditor here...
}
});
Here's how it's done:
await for (final change in _quillEditorController.changes) {
final oldDelta = change.toList()[0];
final changeDelta = change.toList()[1];
final changeSource = change.toList()[2];
if (changeSource == ChangeSource.REMOTE) {
console.log("An API call triggered this change.");
} else if (changeSource == ChangeSource.LOCAL) {
console.log("A user action triggered this change.");
}
}
Note that flutter-quill's api doesn't exactly match quill.js. And since the documentation is lacking, your best hope in understanding what's available is by digging into the code-base using your editor (eg. using "go to definition").

Flutter Bloc Rx dart combineLatest2 combine function not running

I'm writing a flutter app and and using the bloc library. I have a bloc and a cubit, within the state of each is a list of ids of some other documents I need to fetch from firestore. There can be some overlap and some docs are already fetched so I want to get the list of ids from both states, compare them, and then only go to firestore for ones that exist in one but no the other.
I set a new cubit for this:
class CircleRecipesCubit extends Cubit<CircleRecipesState> {
CircleRecipesCubit({
#required RecipesBloc recipesBloc,
#required CirclesCubit circlesCubit,
}) : assert(
recipesBloc != null,
circlesCubit != null,
),
_recipesBloc = recipesBloc,
_circlesCubit = circlesCubit,
super(CircleRecipesInitial());
final RecipesBloc _recipesBloc;
final CirclesCubit _circlesCubit;
StreamSubscription _recipesSubscription;
StreamSubscription _circlesSubscription;
Future<void> getCircleRecipes() async {
// get a list of recipes the user already has loaded
List<String> userRecipesIds;
_recipesSubscription = _recipesBloc.stream.listen((RecipesState event) {
if (event is RecipesLoaded) {
userRecipesIds = event.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
});
// get a list of recipes in the circles
List<String> circleRecipeIds;
_circlesSubscription = _circlesCubit.stream.listen((CirclesState event) {
if (event is CirclesLoaded) {
circleRecipeIds = event.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
});
// reduce the list of recipes to a set of only circle recipes
//TODO
//------- Try with RX dart
Stream<RecipesState> recipesStream = _recipesBloc.stream;
Stream<CirclesState> circlesStream = _circlesCubit.stream;
Rx.combineLatest2(recipesStream, circlesStream, (
RecipesState recipesState,
CirclesState circlesState,
) {
print("This doesn't print!");
print(recipesState);
print(circlesState);
if (recipesState is RecipesLoaded) {
userRecipesIds = recipesState.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
if (circlesState is CirclesLoaded) {
circleRecipeIds = circlesState.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
// fetch the set of recipes
});
}
#override
Future<void> close() {
_recipesSubscription.cancel();
_circlesSubscription.cancel();
return super.close();
}
}
So above is my cubit - it listens to the recipesBloc and the circlesCubit. The first two expressions in the getCiricleRecipes() function are only there to prove that its hooked up correctly - when it runs those print statement print the ids I want it to from both the other bloc and the other cubit.
I need the latest values from both though at the same time to compare them - so I thought rx.combinelatest2 would be good. I give it the stream from the bloc and the cubit. But the combiner function doesn't even run even though things seem 'wired up' correctly.
Any help greatly appreciated.
Make sure both streams have already emitted at least one item.
combineLatest documentation states:
The Stream will not emit until all streams have emitted at least one item.
Since the first block (where you subscribe to _circlesCubit) prints, then most likely _recipesBloc is the culprit here.

in Flutter, make a list of api call inside one api call

In one of my flutter app, at first I want to call an api, which will return a list of item, and the item will be shown in a ListView. I also need to call another api for each item of the ListView to fetch description of that item and show the description to each item according to their id. How can I resolve this scenario. In RxJava, there is an operator called flatmap which did the same things without any hassle. But in flutter, How can I implement this. Here is my 2 function
class HomeRepositoryImpl extends HomeRepository {
HomeGraphQLService homeGraphQLService;
HomeMapper homeMapper;
HomeRepositoryImpl(HomeGraphQLService homeGraphQLService, HomeMapper homeMapper) {
this.homeGraphQLService = homeGraphQLService;
this.homeMapper = homeMapper;
}
#override
Future<List<Course>> getAllCourseOf(String className, String groupName) async {
final response = await homeGraphQLService.getAllCourseOf(className, groupName);
return homeMapper.toCourses(response).where((course) => course.isAvailable);
}
#override
Future<CourseProgressAndPerformance> getProgressAndPerformanceAnalysisOf(String subjectCode) async {
final response = await homeGraphQLService.getProgressAndPerformanceAnalysisOf(subjectCode);
return homeMapper.toProgressAndPerformance(response);
}
}
In the above class, first I call getAllCourseOf() function to get a list of course and show them in list view. I need to call getProgressAndPerformanceAnalysisOf(courseId) to fetch description of each item and show the description in each item of that list.
So what is recommended way to do so.
thanks in advance
I'm not sure on how the listing would be presented, my guess is you're looking for Stream and asyncMap()
Here's an example implementation that would give you a list of CourseProgressAndPerformance, this is the direction I'd investigate.
var perfList = Stream
.fromIterable(listOfCourses)
.asyncMap((course) => getProgressAndPerformanceAnalysisOf(courseId))
.toList();

how to handle multiple `loading` states with redux architecture in Flutter?

How do you guys handle multiple loading states with redux pattern in Flutter?
So here is my case:
I have 3 pages, each page calls a different API and shows a loading HUD while requesting the API.
Sub-Question#1 How do i manage these isLoading states?
If i do it in the AppState , i need to add multiple boolean properties to it, something like:
class AppState {
final bool apiOneIsLoading;
final bool apiTwoIsLoading;
final bool apiThreeIsLoading;
// other properties, bla bla...
}
However, Adding to many properties to the AppState class doesn’t sounds great I guess...
Sub-Question#2 How do I update the UI when the loading state changes?
One solution I come up with is to create actions for both loading and loaded state, like so:
class SomeMiddleware extends MiddlewareClass<AppState> {
#override
void call(Store<AppState> store, dynamic action, NextDispatcher next) {
if (action is CallAPIOneAction) {
store.dispatch(APIOneLoadingAction());
// call api one
api.request().then((result){
store.dispatch(APIOneLoadedAction())
})
}
}
}
But if I do this, I need to create 2 extra actions for each API call, is this a good idea? is it okay to change or send new actions in middleware class on the fly?
Please let me know if you have a good solution, thanks!