The streamDemo() is not updating the value of doc('$mainDocId') when the value of mainDocId is updated dynamically. I want to update the widget HomeBody() when the document Id is changed dynamically so that I can retrieve the data as per documents selected by users.
I'm using getx as SM. I tried to update the value with update() method but not working.
The codes are as follows.
Controller:
class Controller extends GetxController {
// onInit
@override
void onInit() {
finalNewsModel.bindStream(streamDemo());
super.onInit();
}
// list of document ids.
List docIdList = [
'USA',
'New York',
'Canada',
];
//
RxString mainDocId = 'USA'.obs;
// method to change document id based on docId index.
changeDocId(int index) {
mainDocId(docIdList[index]);
}
//
Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>();
//
List<NewsModel> get newsModelList => finalNewsModel.value;
//
Stream<List<NewsModel>> streamDemo() {
return FirebaseFirestore.instance
.collection('news')
.doc('$mainDocId')
.snapshots()
.map((ds) {
var mapData = ds.data();
List mapList = mapData['list'];
List<NewsModel> modelList = [];
mapList.forEach((element) {
modelList.add(NewsModel.fromMap(element));
});
return modelList;
});
}
}
// UI
class HomeBody extends StatefulWidget {
@override
_HomeBodyState createState() => _HomeBodyState();
}
class _HomeBodyState extends State<HomeBody> {
//
final Controller _controller = Get.put<Controller>(Controller());
@override
Widget build(BuildContext context) {
return Container(
child: Obx(() {
if (_controller.newsModelList == null) {
return Center(
child: Text(
'Please try later!',
));
} else if (_controller.newsModelList.isEmpty) {
return Text('Empty List');
} else {
return ListView.builder(
itemCount: _controller.newsModelList.length,
itemBuilder: (context, index) {
final NewsModel _newsModel = _controller.newsModelList[index];
return MyContainer(
title: _newsModel.title,
titleImage: _newsModel.titleImage,
index: index,
);
},
);
}
}),
);
}
}
BottomNavBar:
bottomNavigationBar: Container(
color: Colors.grey[300],
height: 60.0,
child: Padding(
padding: EdgeInsets.all(8.0),
child: GetBuilder<Controller>(
builder: (context) => ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: _controller.docIdList.length,
itemBuilder: (context, index) {
return FavCategoryTags(
tagName: _controller.docIdList[index],
onpress: () =>_controller.changeDocId(index),
);
},
),
),
),
),
Model:
class NewsModel {
String title, titleImage, brief, source;
List aList;
NewsModel({this.title, this.titleImage, this.brief, this.aList, this.source});
factory NewsModel.fromMap(dynamic fieldData) {
return NewsModel(
title: fieldData['title'],
titleImage: fieldData['titleImage'],
brief: fieldData['brief'],
aList: fieldData['mediaDescList'],
source: fieldData['source'],
);
}
}
No StreamBuilder
needed here. Once a regular Stream
is binded to an RxType
you're good to go. It will update when the stream data changes. You just need to update the binding call as demonstrated below.
One issue is that you should just initialize to a regular RxList
.
Instead of this
Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>();
Initialize it like this.
RxList<NewsModel> finalNewsModel = <NewsModel>[].obs;
You can then lose this getter
List<NewsModel> get newsModelList => finalNewsModel.value;
because .value
isn't needed and won't work on a properly initialized RxList
. You can treat an RxList
list like a regular list, as opposed to RxString
, RxInt
etc... that need .value
.
Your Obx
can now build on finalNewsModel
and you can lose the null check because finalNewsModel
is initialized to an empty list, and will never be null.
Obx(() {
if (_controller.finalNewsModel.isEmpty) {
return Text('Empty List');
} else {
return Expanded(
child: ListView.builder(
itemCount: _controller.finalNewsModel.length,
itemBuilder: (context, index) {
final NewsModel _newsModel =
_controller.finalNewsModel[index];
return MyContainer(
title: _newsModel.title,
titleImage: _newsModel.titleImage,
index: index,
);
},
),
);
}
}),
As for what you're trying to do with your BottomNavBar
:
Here you're trying to change the parameter of the Stream itself, by changing the Document Id
. When you binded to the Stream in onInit
it binded to whatever Document Id
was set to at the time. So it will only update for changes within that Document
unless you bind it to a new Stream. So in your case, just call finalNewsModel.bindStream(streamDemo());
again in the changeDocId()
method to update the Stream parameters.
void changeDocId(int index) {
mainDocId(docIdList[index]);
finalNewsModel.bindStream(streamDemo()); // only needed because you're updating the Document Id
}
You also don't need a GetBuilder
in your BottomNavBar
unless you need something visually to change on the BottomNavBar
itself. All you're doing is updating the value of an RxString
based on the value of a hard coded List
. Assuming you did need something in the BottomNavBar
to rebuild, that would be the only scenario you would need to call update()
.
I don't have your full Firebase collection structure, but I tested it with a simplified NewsModel
and updating a String
in the Firebase console updates the Obx
widget immediately. And calling changeDocId()
immediately returns the value of the updated Document Id
.
EDIT: Also, for what you're doing, mainDocId
doesn't need to be an observable string. A stream will always be more expensive than a primitive data type so unless you can justify it, best just to make it a regular string like below. It works exactly the same.
String mainDocId = 'USA';
// method to change document id based on docId index.
void changeDocId(int index) {
mainDocId = docIdList[index];
finalNewsModel.bindStream(streamDemo());
}