Getx How to refresh list by using Obx - flutter

I'm working with ReorderableSliverList but I have no idea how to observe the list based on my data dynamically.
Screen 1
ReorderableSliverList(
delegate: ReorderableSliverChildBuilderDelegate(
(BuildContext context, int index) {
final data = controller.products[index];
return ItemView(data);
},
childCount: controller.products.length),
onReorder: _onReorder,
)
At screen2 will have a add button to call controller insert new data into list
controller
var products = List<Product>.empty().obs;
void add(String name) {
if (name != '') {
final date = DateTime.now().toIso8601String();
ProductProvider().postProduct(name, date).then((response) {
final data = Product(
id: response["name"],
name: name,
createdAt: date,
);
products.add(data);
Get.back();
});
} else {
dialogError("Semua input harus terisi");
}
}
The code above need to click Hot reload in order to show data in screen 1 if data has changed from screen 2.
I'm trying to use Obx to make it refresh automatically but the result it still the same.
Code
ReorderableSliverList(
delegate: ReorderableSliverChildBuilderDelegate(
(BuildContext context, int index) {
final data = controller.products[index];
return Obx(
() => controller.products.isEmpty
? Center(
child: Text("BELUM ADA DATA"),
)
: ItemView(data)
);
}, childCount: controller.products.length),
onReorder: _onReorder,
)

You need to wrap the whole ReorderableSliverList with Obx like this:
Obx(()=>ReorderableSliverList(
...
...
));

Related

Flutter Riverpod StateNotifier initialize state is empty but whenever buildMethod rebuild it's not empty

I got a HiveBox and I want to access it with Riverpod StateNotifier.
This is how I defined this provider:
final hiveSalonProvider =
StateNotifierProvider<HiveSalonNotifier, List>((ref) {
return HiveSalonNotifier();
});
Then i created a StateNotifier class which it's listening to list of SalonModel class.
class HiveSalonNotifier extends StateNotifier<List<SalonModel>> {
HiveSalonNotifier([List<SalonModel>? state])
: super(state ?? <SalonModel>[]) {
_cacheManager = SalonCacheManager('boxB');
fetchDatasFromHiveBox();
}
late final CacheManagerBase<SalonModel> _cacheManager;
List<SalonModel>? salonItems = [];
Future<void> fetchDatasFromHiveBox() async {
await _cacheManager.init();
if (_cacheManager.getValues()?.isNotEmpty ?? false) {
state = _cacheManager.getValues()!;
salonItems?.addAll(state);
print('provider: datas from caches');
} else {
print('provider:provider: datas from services');
}
}
It seems there is no error. I think so there is not.
But in UI (StatelessWidget);
In build method, I have defined our provider:
var hive = ref.read(hiveSalonProvider.notifier);
In Column:
(hive.salonItems?.isNotEmpty ?? false)
? ListView.builder(
shrinkWrap: true,
itemCount: hive.salonItems?.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const CircleAvatar(),
title: Text(
'${hive.salonItems?[index].salonName.toString()}'),
);
},
)
: const CircularProgressIndicator(color: Colors.white),
At first hot reload, this widget showing me CircularProgressIndicator. But then I press the save code combination from keyboard (CTRL+S in vscode), it's showing listView correctly.
Where is the problem ?

Flutter: StreamBuilder with Firebase

I have a ListView of objects from Firebase in which I would like to have it refresh using a StreamBuilder when the data changes.
I can load up my list fine & when data changes my list does refresh.
The issue I am having is instead of the ListTile that has the change just updating, I see that tile being duplicated so I see the new change & the old change.
Here's my setup:
final ref = FirebaseDatabase.instance.reference();
late DatabaseReference itemRef;
late FirebaseDatabase database = FirebaseDatabase();
late StreamSubscription _objectInfoStreamSub; // Not sure if needed?
late List<CustomObject> data = [];
#override
void initState() {
super.initState();
final keys = Global.kData.keys;
for (final key in keys) {
// Initialize this...
itemRef = database.reference().child('ABC').child(key.toString());
}
// Load the data...
_setListeners();
}
// Here is where I load my data initially...
Future<void> _setListeners() async {
// Clear out our data before we reload...
data.clear();
final keys = Global.kData.keys;
for (final key in keys) {
_objectInfoStreamSub =
ref.child("ABC").child(key.toString()).onValue.listen(
(event) {
setState(() {
// Mapped the data...
final firebaseData = Map<String, dynamic>.from(event.snapshot.value);
// Create the Room...
final room = CustomObject.fromJson(firebaseData);
// Check if blocked...
// Logic to see if user is blocked
// check if isArchived
// Logic to see if room is archvied
if (!isBlocked && !isArchived) {
if (!data.contains(room)) {
data.add(room);
}
}
// Sort by date
// Logic to sort so the most recent is at top
});
},
);
}
}
// Here is my updated StreamBuilder...
SliverToBoxAdapter(
child: StreamBuilder<dynamic>(
stream: itemRef.child('ABC').onValue,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return ChatRoom(
data: data[index],
);
},
);
} else {
return Container();
}
},
),
),
Not sure if this causes your problem, but try wrapping the ListView.builder() with StreamBuilder instead of only it's items. Because in your current state of code, if you would add another item and your data.length would change, the ListView.builder() wouldn't get rebuilt and it wouldn't build new data.

Change List Tile trailing using provider

The problem is that when a List Tile is tapped the quantity is incremented for all the list tiles.
I have a stateless widget which has this build method :
final ProductsList productsList = ProductsList(context);
return Scaffold(
body: Center(child: productWidget(productsList, args)));
}
This is the ProductWidget
FutureBuilder productWidget(productsList) {
return FutureBuilder(
future: getProducts,
builder: (context, products) {
switch (products.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
return Scaffold(
body: productsList.build(products.data));
}
},
);
And this is what productsList.build does:
ProductsList(this.context);
Padding getProduct(name) {
int _quantity = Provider.of<Quantity>(context).getQuantity();
return ListTile(
key: UniqueKey(),
onTap: () {
Provider.of<Quantity>(context, listen: false).incrementQuantity();
},
title: Text(name),
trailing: Text("$_quantity"),
),
);
}
ListView build(products) {
List<Widget> _products = new List();
for (var i = 0; i < products.length; i++) {
_products.add(getProduct(products[i].name));
}
return ListView(
children: _products,
);
}
and I am using this changeNotifier :
class Quantity extends ChangeNotifier {
int _quantity = 0;
void incrementQuantity(){
_quantity += 1;
notifyListeners();
}
int getQuantity() {
return _quantity;
}
}
I want to tap a list tile and increment just it's value which is displayed in the trailing, but not of the others.
I am using multi-provider in the main file of the application.
Provider needs to track quantity by product. Your Provider is tracking quantity as a single int so the result you are seeing is correct for your code.
Quantity should be List. You can also set the initial value.
Then
incrementQuantity(int index) {
increment quantity[index] here
}
And
get quantity(int index){
return quantity[index]
}
On a side note, in my opinion, your efforts would benifit greatly by researching using ListTile with Provider.

How to append new data in ListView Flutter

I Wanna ask for my problem in flutter. How to append new data from another screen (has pop) to main screen (contain list view data from json) this is my code from main screen.
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FormUser()))
.then((VALUE) {
/////////
Here for adding new data from VALUE into list view
/////
});
});
This my listview
Widget _buildListView(List<User> users) {
return ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(color: Colors.grey[400]),
itemCount: users.length,
itemBuilder: (context, index) {
User user = users[index];
return ListTile(
leading: Icon(Icons.perm_identity),
title: Text(capitalize(user.fullName)),
subtitle: Text(user.gender),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(user.grade.toUpperCase()),
Text(user.phone)
],
),
);
},
);
}
}
This is return data from add screen
User{id: null, fullname: Camomi, gender: pria, grade: sma, phone: 082232, email: ade#gmul.com}
This is my class model of typedata
class User {
int id;
String fullName, gender, grade, phone, email;
User(
{this.id,
this.fullName,
this.gender,
this.grade,
this.phone,
this.email});
}
You add an element to your list of users. Then you call setState so your widget's build function gets called and builds the new view with the list of users that now contains the new element.
And please do not use .then() in your onPressed method. Make the method async and use await or if you really want to use .then(), at least return the Future it gives you.
You will need to do extra steps:
Wait for the result when calling push() method:
final result = await Navigator.push(...);
In your FormUser widget, when you finish entering data and press on Done or similar, you should return the created user:
// userData will be assigned to result on step 1 above. Then you add that result (user) to your list
Navigator.pop(context, userData);
You can find a very good tutorial here.
First of all make sure that Your class extends StatefulWidget.
I assume your list of users looks like this (or it's empty like [] ) :
List<User> users = [user1, user2, user3];
In Your FloatingActionButton in onPressed you should have this method (copy the content to onPressed or write a method like this somewhere in your code).
The method must be async to await the user
void _addUser() async {
final user = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FormUser()));
if (user != null) {
setState(() => users.add(user));
}
}
This code should work fine, enjoy :)

How to solve the index in list view is not correct

I am using list view + pagination in Flutter to show my response data.
I face the problem when I selected the first list item the details such as id is the other list item id. I have used print() to check the id, it always shows the wrong id.
I want to show the details about my image, but it gives me wrong id. So it will show other image.
How can I solve the problem?
There is no need to define id and title as variables of the State object.
You can pass them as a parameter to the selectedItem method instead, the problem is you always set the id and title to the last item built so it will always navigate with its details instead of the actually selected item.
class _HomePage State extends State<HomePage > {
GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();
#override
Widget build(BuildContext context) {
body: return Paginator.listView(
key: paginatorGlobalKey,
pageLoadFuture: sendPagesDataRequest,
pageItemsGetter: listItemsGetterPages,
listItemBuilder: listItemBuilder,
loadingWidgetBuilder: loadingWidgetMaker,
errorWidgetBuilder: errorWidgetMaker,
emptyListWidgetBuilder: emptyListWidgetMaker,
totalItemsGetter: totalPagesGetter,
pageErrorChecker: pageErrorChecker,
scrollPhysics: BouncingScrollPhysics(),
);
}
Future<PagesData> sendPagesDataRequest(int page) async {
String url = Uri.encodeFull("https://API_URL?page=$page");
http.Response response = await http.get(url);
PagesData pagesData = pagesDataFromJson(response.body);
return pagesData;
List<dynamic> listItemsGetterPages(PagesData pagesData) {
List<Project> list = [];
pagesData.data.forEach((value) {
list.add(value);
});
return list;
}
Widget listItemBuilder(dynamic item, int index) {
return InkWell(
onTap: () => selectedItem(item,context), // pass the item iteself in the selectedItem function
child: new CachedNetworkImage(
imageUrl:= item.image,
placeholder: (context, url) => new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
fit:BoxFit.fill,
),
);
}
Widget loadingWidgetMaker() {
return Container(
alignment: Alignment.center,
height: 160.0,
child: CircularProgressIndicator(),
);
}
void selectedItem(dynamic item,BuildContext context) { // add a parameter for item
Navigator.of(context).pushNamed(
DetailsPage.routeName,
arguments: {
'id': item.id, // Move the item.id here
'title': item.title // Move the item.title here
});
}
}