Merge multiple GetX controllers into one stream - flutter

I want to use multiple GetX controllers in my widget. The StreamBuilder of the async package offers the possibility to combine streams like in the code below. Is there any way to use this approach with the GetX? I would not like to nest the GetX as this leads to ugly code.
#override
Widget build(BuildContext context) {
return StreamBuilder<List<dynamic>>(
stream: CombineLatestStream.list(
[
_profileStore.getProfileStream(),
_journalStore.getJournalStream(),
],
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final Profile profile = snapshot.data![0];
final List<Dream> journal = snapshot.data![1];
...
}
);
}
My current widget with GetX looks like this.
#override
Widget build(BuildContext context) {
return GetX<ProfileStore>(
builder: (store) {
return Text('${store.profile.value.username}');
}
);
}

Use the CombineLatestStream.list([]) function from rxdart documentation to create a new stream from it.
Stream stream = CombineLatestStream.list([...]);
Then, bind that new stream to the variable or observable you want to update:
var.bindStream(stream);

Lars above mentioned the most of thing, just want to add my way of updating variable using GetX
CombineLatestStream combinedStream =
CombineLatestStream.list(multipleStreams);
try {
combinedStream.listen((values) {
Map<String, dynamic> localMap = {};
(values as List).forEach((element) {
localMap[key]] = element;
});
rxVar.addAll(localMap);
});
} catch (e) {
log("Err : binding stream $e");
}

Related

How to dynamically update ListView while fetching data asynchronously?

I am trying to figure out the best way to update a ListView.builder() while I'm fetching a list of data. Essentially, I am downloading data in batches -- let's say a group of 10 images at a time -- and displaying them in a ListView.builder after the future completes, with an indicator below it to signify that we're still fetching data. And do this until everything is fetched.
What's the best way of going about this?
Example code of what I have:
void _fetchImages() async {
// Fetch images
for (...) {
final results = await Future.wait[imageFutures];
// update list here
imageList.addAll(results); // let's say data comes back in correct format
setState((){});
}
}
#override
void initState() {
super.initState();
_fetchImages();
}
#override
Widget build(BuildContext context) {
return ListView.builder(...);
}
Return List from Stream then you can use StreamBuilder.
return StreamBuilder<List<MyImage>>(
stream: dataStream,
builder: (BuildContext context, AsyncSnapshot<List<MyImage>> snapshot) {
if (snapshot.hasData) {
return Center(
child: ListView.builder(
...
);
}
return SomeWidget(
...
);
},
);

streamBuilder not updating itsself after sorting or filtering - Flutter

I am using StreamBuilder and ListView to show data from FireStoreDatabase in UI. Usually, it updates itself when I make any change in the database (without refresh). but when I sort the data in the stream, it no longer updates. here's the code am using for StreamBuilder.
Stream<List<Attraction>> attractionsStream = Stream.value([]);
void initState() {
final database = Provider.of<Database>(context, listen: false);
setState(() {
//this.attractions = attractions.toList();
attractionsStream = database.attractionStream();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<Attraction>>(
stream: attractionsStream,
builder: (context, snapshot) {...}
);
}
_sortData() async {
final db = Provider.of<Database>(context, listen: false);
var attractions = await db.attractionStream().first;
attractions.sort((a, b) {...}
setState(() {
attractionsStream = Stream.value(attractions);
});
}
Could you please guide me on this?
Try adding a unique Key to each widget that your StreamBuilder builds.
It is also uncommon to completely reset the stream, as opposed to pushing new data through the stream already in use.

Flutter set state not updating my UI with new data

I have a ListView.builder widget wrapped inside a RefreshIndicator and then a FutureBuilder. Refreshing does not update my list, I have to close the app and open it again but the refresh code does the same as my FutureBuilder.
Please see my code below, when I read it I expect the widget tree to definitely update.
#override
void initState() {
super.initState();
taskListFuture= TaskService().getTasks();
}
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(builder: (context, taskData, child) {
return FutureBuilder(
future: taskListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
taskData.tasks = (snapshot.data as ApiResponseModel).responseBody;
return RefreshIndicator(
onRefresh: () async {
var responseModel = await TaskService().getTasks();
setState(() {
taskData.tasks = responseModel.responseBody;
});
},
child: ListView.builder(
...
...
Let me know if more code is required, thanks in advance!
Points
I am using a StatefulWidget
Task data is a class that extends ChangeNotifier
When I debug the refresh I can see the new data in the list, but the UI does not update
getTasks()
Future<ApiResponseModel> getTasks() async {
try {
var _sharedPreferences = await SharedPreferences.getInstance();
var userId = _sharedPreferences.getString(PreferencesModel.userId);
var response = await http.get(
Uri.parse("$apiBaseUrl/$_controllerRoute?userId=$userId"),
headers: await authorizeHttpRequest(),
);
var jsonTaskDtos = jsonDecode(response.body);
var taskDtos= List<TaskDto>.from(
jsonTaskDtos.map((jsonTaskDto) => TaskDto.fromJson(jsonTaskDto)));
return ApiResponseModel(
responseBody: taskDtos,
isSuccessStatusCode: isSuccessStatusCode(response.statusCode));
} catch (e) {
return null;
}
}
The issue here seems to be that you are updating a property that is not part of your StatefulWidget state.
setState(() {
taskData.tasks = responseModel.responseBody;
});
That sets a property part of TaskData.
My suggestion is to only use the Consumer and refactor TaskService so it controls a list of TaskData or similar. Something like:
Provider
class TaskService extends ChangeNotifier {
List<TaskData> _data;
load() async {
this.data = await _fetchData();
}
List<TaskData> get data => _data;
set data(List<TaskData> data) {
_data = data;
notifyListeners();
}
}
Widget
class MyTaskList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskService>(builder: (context, service, child) {
return RefreshIndicator(
onRefresh: () {
service.getTasks();
},
child: ListView.builder(
itemCount: service.data.length,
itemBuilder: (BuildContext context, int index) {
return MyTaskItem(data:service.data[index]);
},
),
);
});
}
}
and make sure to call notifyListeners() in the service.getTasks() method to make the Consumer rebuild
I think (someone will correct me if I'm wrong) the problem is that you are using the FutureBuilder, once it's built, you need to refresh to whole widget for the FutureBuilder to listen to changes. I can suggest a StreamBuilder that listens to any changes provided from the data model/api/any kind of stream of data. Or better yet, you can use some sort of state management like Provider and use Consumer from the Provider package that notifies the widget of any changes that may occurred.

How to display a Firebase list in REAL TIME using BLoC Pattern?

I have a TODO List function (Alarmas), but I feel I'm not taking advantage of Firebase's Realtime features enough.
The Widget displays the list very well, however when someone puts a new task from another cell phone, I am not being able to show it automatically, but I must call the build again by clicking on the "TODO button" in the BottomNavigationBar.
Is there a way that the new tasks are automatically displayed without doing anything?
I'm using BLOC Pattern and Provider to get Data through Streams...
#override
Widget build(BuildContext context) {
alarmaBloc.cargarAlarmas();
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
if (tareasList.length == 0) return _imagenInicial(context);
return ListView(
children: [
for (var itemPendiente in tareasList)
_crearItem(context, alarmaBloc, itemPendiente),
//more widgets
],
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center (child: Image(image: AssetImage('Preloader.gif'), height: 200.0,));
},
),
#puf published a solution in How to display a Firebase list in REAL TIME? using setState, but I don't know how to implement it because I can't use setState inside my BLoC pattern page.
UPDATE
My BLoC Pattern looks like this...
class AlarmaBloc {
final _alarmaController = new BehaviorSubject<List<AlarmaModel>>();
final _alarmaProvider = new AlarmaProvider();
Stream <List<AlarmaModel>> get alarmasStream => _alarmaController.stream;
Future<List<AlarmaModel>> cargarAlarmas() async {
final alarmas = await _alarmaProvider.cargarAlarmas();
_alarmaController.sink.add(alarmas);
return alarmas;
}
//---
dispose() {
_alarmaController?.close();
}
And my PROVIDER looks like this...
Future<List<AlarmaModel>> cargarAlarmas() async {
final List<AlarmaModel> alarmaList = new List();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
print('Provider - Nuevo onChild Alarma ${element.snapshot.value['fecha']} - ${element.snapshot.value['nombreRefEstanque']} - ${element.snapshot.value['pesoPromedio']}}');
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp); // element.snapshot.value.
});
await resp.once().then((snapshot) {
print("Las Alarmas se cargaron totalmente - ${alarmaList.length}");
});
return alarmaList;
How can I display a List from Firebase in "true" Real Time using BLoC Pattern?

How to read Firebase realtime database as "string"(without Widget, streambuilder) in Flutter?

I want to read firebase RTDB without using streambuilder.
Can I use the future or provider? If so, how should I approach it?
DatabaseReference databaseReference = FirebaseDatabase().reference();
class _BatteryStateReaderState extends State<BatteryStateReader> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: databaseReference.child("robot").onValue,
builder: (context, AsyncSnapshot<Event> snap) {
if (!snap.hasData) return Text("loading...");
return Text(snap.data.snapshot.value["battery"].toString());
},
);
}
}
firebase database:
{
robot: {
battery : 88
}
}
To get a Future<DataSnapshot> instead of a stream of events, you can use the once() call on the DatabaseReference. See the FlutterFire documentation on the once() method and this snippet from the example of that library:
database.reference().child('counter').once().then((DataSnapshot snapshot) {
print('Connected to second database and read ${snapshot.value}');
});