not able to fetch data by ID in flutter using Getx - flutter

i'm trying to fetch a list of sub categories based on category id, for example:
Category A has 3 sub categories: A1 - A2 - A3
My backend works fine, I pass the category_id to the function and it returns me a list of sub categories having the category_id.
Since i'm new to getx, I tried passing the category_id as a route parameter but i'm not able to show the list of sub categories. In fact I didn't get how to pass the category_id while in the UI.
Here is my repo:
Future<Response> getSubCategoriesListByCategory(int categ_id) async {
return await apiClient.getData('shop/category/$categ_id');
}
Here is my controller:
List<dynamic> _subCategoriesListByCategory = [];
List<dynamic> get subCategoriesListByCategory => _subCategoriesListByCategory;
bool _isLoaded = false;
bool get isLoaded => _isLoaded;
Future<void> getSubCategoriesByCategoryId(int cat_id) async {
Response response =
await subCategoriesRepo.getSubCategoriesListByCategory(cat_id);
if (response.statusCode == 200) {
_subCategoriesListByCategory = [];
_subCategoriesListByCategory.addAll(response.body);
_isLoaded = true;
//print(categoriesList);
update(); // = setState();
} else {}
}
Here is my RouteHelper:
GetPage(
name: subCategory,
page: () {
var catId = Get.parameters['catId'];
return SubCategoriesPage(catId: int.parse(catId!));
},
transition: Transition.fadeIn),
And here is my UI:
GetBuilder<SubCategoriesController>(builder: (subCategories) {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: ScrollPhysics(),
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 90 / 100,
padding: EdgeInsets.all(16),
children: List.generate(
subCategories.subCategoriesListByCategory.length, (index) {
return _buildSingleSubCategory(
index,
SubCategoryModel.fromJson(
subCategories.subCategoriesListByCategory[index]));
}),
);
})
Code from home page where i'm passing the category_id:
onTap: () {
Get.toNamed(RouteHelper.getSubCategory(category.id));
},
I'm able to print the clicked category's id in the subs page which means it's passed correctly, also i'm getting GOING TO ROUTE /sub-category?catId=3
Noting that i'm priting the specific category_id correctly in the sub categories page, I couldn't fetch the specific data related to them. Any suggestion on how to solve this?

I'm not sure if this helps you since I haven't seen your full code, but I'm guessing you want to add this as parameter to your GetBuilder
initState: (state) => state.controller?.getSubCategoriesByCategoryId(widget.cat_id),

Solved it by adding: Get.find<SubCategoriesController().getSubCategories(widget.catId); inside the GetBuilder()

Related

Filter Item Listview whit TextField

Hello I have filled a ListView from list on my State Bloc(CustomerGetAllLoadedState) and work fine but now I need to search item from a TextField, I did so:
I declare list:
List<Customer> _customersFromRepo = [];
this is ListView where intercept to List Global:
BlocBuilder<CustomerBloc, CustomerState>(
builder: (context, state) {
if (State is CustomerLoadingState) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is CustomerGetAllLoadedState) {
_customersFromRepo = state.customers; // <----------- List for searh method
return SizedBox(
height: h * 0.5,
width: w * 0.5,
child: _customersFromRepo.isNotEmpty ? ListView.builder(
itemCount: _customersFromRepo.length,
itemBuilder: (context, index) => Card(
key: ValueKey(
_customersFromRepo[index].id),
this is TextField for search items:
CustomTextFormField(
txtLable: "Nome Cliente",
onChanged: (value) => _runFilter(value)
this is method fo filter:
void _runFilter(String enteredKeyword) {
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = _customersFromRepo;
} else {
results = _customersFromRepo
.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
setState(() {
_customersFromRepo = results;
});
But the list doesn't change even if _customersFromRepo has only one item, it always keeps the old state. Can I do?
Update: So I changed the approach, filtered the list and then issued a block event with the List retrieved from the Filter and reissued the status loading all the Customers, Filter works but I have a problem when I fill in the word I need to search for it starts filtering but if I go back it should unroll the filter but it doesn't:
_runFilter(BuildContext context,String enteredKeyword) {
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = _customersFromRepo;
} else {
results = _customersFromRepo
.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
return context.read<CustomerBloc>().add(CustomerEventemitFilteredCustomer(results));
}
On thhe textField where input data for filter I used OnChane()
I resolved Post My Solution all you. I have Loaded from Repository List for Filter,the result put in to Event and reloaded State with the filter.
_runFilter(BuildContext context,String enteredKeyword) async{
final List<Customer> customerd = await CustomerRepository(customerService: CustomerService()).getAllCustomers();
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = customerd;
} else {results =
customerd.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
context.read<CustomerBloc>().add(CustomerEventemitFilteredCustomer(results));
}

Create infinite scrolling effect using Firebase Realtime Database data in flutter

I am integrating a chat feature in my mobile application, and decided to use Firebase Realtime Database for the backend instad of Firestore as a cost reduction mechanism. I am running into a problem, however. There seems to be very sparse documentation on how to create infinite scrolling using data from Realtime Database instead of Firestore.
Below is the organization of my chat messages. This is the query I want to use:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
And this is the widget I want to return for each result:
MessageWidget(
message: message.text,
id: message.uid,
name: message.name,
lastSender: message.lastSender,
date: message.timeStamp,
profilePicture: message.profilePicture,
);
Here is the database structure
The query works, and I have already programmed the MessageWidget from the JSON response of the query. All I need is for the query to be called whenever it reaches the top of its scroll, and load more MessageWdigets. Also note, this is a chat app where users are scrolling up, to load older messages, to be added above the previous.
Thank you!
EDIT: here is the code I currently have:
Flexible(
child: StreamBuilder(
stream: FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.limitToLast(20)
.onValue,
builder:
(context, AsyncSnapshot<DatabaseEvent> snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
} else {
Map<dynamic, dynamic> map =
snapshot.data!.snapshot.value as dynamic;
List<dynamic> list = [];
list.clear();
list = map.values.toList();
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ListView.builder(
controller: _scrollController,
// shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
final json = list[index]
as Map<dynamic, dynamic>;
final message = Message.fromJson(json);
return MessageWidget(
message: message.text,
id: message.uid,
name: message.name,
lastSender: message.lastSender,
date: message.timeStamp,
profilePicture:
message.profilePicture,
);
}),
),
);
}
},
),
),
My initState
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
//add more messages
} else {
print('At the bottom');
}
}
});
}
Your code already loads all messages.
If you want to load a maximum number of messages, you'll want to put a limit on the number of messages you load. If you want to load only the newest messages, you'd use limitToLast to do so - as the newest messages are last when you order them by their timeStamp value.
So to load for example only the 10 latest messages, you'd use:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.limitToLast(10);
This gives you the limited number of messages to initially show in the app.
Now you need to load the 10 previous messages when the scrolling reaches the top of the screen. To do this, you need to know the timeStamp value and the key of the message that is at the top of the screen - so of the oldest message you're showing so far.
With those two values, you can then load the previous 10 with:
FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.endBefore(timeStampValueOfOldestSeenItem, keyOfOldestSeenItem)
.limitToLast(10);
The database here again orders the nodes on their timeStamp, it then finds the node that is at the top of the screen based on the values you give, and it then returns the 10 nodes right before that.
After several days of testing code, I came up with the following solution
The first step is to declare a ScrollController in your state class.
final ScrollController _scrollController = ScrollController();
You will also need to declare a List to store query results
List list = [];
Next, use the following function to get initial data
getStartData() async {
//replace this with your path
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('messages/${widget.placeID}');
starCountRef
.orderByChild("timeStamp")
//here, I limit my initial query to 6 results, change this to how many
//you want to load initially
.limitToLast(6)
.onChildAdded
.forEach((element) {
setState(() {
list.add(element.snapshot.value);
list.sort((a, b) => a["timeStamp"].compareTo(b["timeStamp"]));
});
});
}
Run this in initState
void initState() {
super.initState();
FirebaseDatabase.instance.setPersistenceEnabled(true);
getStartData();
}
Now to display the initial data that was generated when the page was loaded, build the results into a ListView
ListView.builder(
itemCount: list.length,
controller: _scrollController,
//here I use a premade widget, replace MessageWidget with
//what you want to load for each result
itemBuilder: (_, index) => MessageWidget(
message: list[index]["text"],
date: list[index]["timeStamp"],
id: list[index]["uid"],
profilePicture: list[index]["profilePicture"],
name: list[index]["name"],
lastSender: list[index]["lastSender"],
),
),
Note that your ListView must be constrained, meaning that you can scroll to the beginning or end of your ListView. Sometimes, the ListView won't have enough data to fill and be scrollable, so you must declare a height with a Container or bound it to its contents.
Now you have the code that fetches data when the page is loaded using getStartData() and initState. The data is stored in list, and a ListView.builder builds a MessageWidget for each item returned by getStartData. Now, you want to load more information when the user scrolls to the top.
getMoreData() async {
var moreSnapshots = await FirebaseDatabase.instance
.ref("messages/${widget.placeID}")
.orderByChild("timeStamp")
.endBefore(list[0]["timeStamp"])
.limitToLast(20)
.once();
var moreMap = moreSnapshots.snapshot.value as dynamic;
setState(() {
list.addAll(moreMap.values.toList());
list.sort((a, b) => a["timeStamp"].compareTo(b["timeStamp"]));
});
}
Then, make the function run when the ListView.builder is scrolled all the way to the top by adding this to the already existing initState.
_scrollController.addListener(() {
if (_scrollController.position.atEdge) {
bool isTop = _scrollController.position.pixels == 0;
if (isTop) {
getMoreData();
}
}
});
Hopefully this helps or gives you a pointer on where to go from here. Thanks to Frank van Puffelen for his help on which query to use based on the previous answer.

How to edit/delete item in listview.builder in different classes

I've tried
setState(() => queueData.removeAt(widget.number-1);
button from QueueGenerator class
TextButton(
onPressed: () {
Navigator.pop(context, "OK");
setState(() {
// remove and update listview ?
queueData[widget.number-1] = _controller
.text
.toString();
});
},
child: Text("Confirm",
style: Theme.of(context).textTheme.labelSmall))
lists for listview.builder (this is in different file from both classes)
List<String> queueData = [];
List<String> queueTemp = [];
Listview.builder
ListView.builder(
itemCount: qList.length,
itemBuilder: (context, index) {
return QueueGenerator(
number: index + 1,
description: qList[index]);
})
well you can either make these two lists
List<String> queueData = [];
List<String> queueTemp = [];
as global variables and use them there without passing them as a parameter in the widget
fast solution but I don't recommend it
or you need to put them in the provider as private variables and create functions of getters and setters for them so you can edit them
note: when you call the provider which they are in it make sure the listen is true to see the changes

ValueListenableBuilder is not rebuilding the screen, when hotreloading, it is working

I'm trying to build a note app, all data and other things is working perfectly, cos the data is displaying to the screen when the code file is saving, its weird , first time facing this problem
in short, the valuelistanble is not listening when the data adding from app, but when just hot reloading the data is displaying
how can i fix this,
here is the code
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance!.addPostFrameCallback((_) async {
final value = await NoteDB.instance.getAllNotes();
});
____________________________________________
____________________________________________
//code line for aligment
Expanded(
child: ValueListenableBuilder(
valueListenable: NoteDB.instance.noteListNotifier,
builder: (context, List<NoteModel> newNotes, _) {
return GridView.count(
childAspectRatio: 3 / 4,
crossAxisCount: 2,
mainAxisSpacing: 34,
crossAxisSpacing: 30,
padding: const EdgeInsets.all(20),
//generating list for all note
children: List.generate(
newNotes.length,
(index) {
//setting the notelist to a variable called [note]
final note = newNotes[index];
if (note.id == null) {
//if the note's id is null set to sizedbox
//the note id never be null
const SizedBox();
}
return NoteItem(
id: note.id!,
//the ?? is the statement (if null)
content: note.content ?? 'No Content',
title: note.title ?? 'No Title',
);
},
),
);
},
)),
here is the NoteDB.instance.getAllNotes(); function
#override
Future<List<NoteModel>> getAllNotes() async {
final _result = await dio.get(url.baseUrl+url.getAllNotes);
if (_result.data != null) {
final noteResponse = GetAllNotes.fromJson(_result.data);
noteListNotifier.value.clear();
noteListNotifier.value.addAll(noteResponse.data.reversed);
noteListNotifier.notifyListeners();
return noteResponse.data;
} else {
noteListNotifier.value.clear();
return [];
}
}
and also there is a page to create note , and when create note button pressed there is only one function calling here is function
Future<void> saveNote() async {
final title = titleController.text;
final content = contentController.text;
final _newNote = NoteModel.create(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
content: content,
);
final newNote = await NoteDB().createNote(_newNote);
if (newNote != null) {
print('Data Added to the DataBase Succesfully!');
Navigator.of(scaffoldKey.currentContext!).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => HomePage()),
(Route<dynamic> route) => false);
} else {
print('Error caught while data adding to the DataBase');
}
}
everything work fine, but while add the data the UI isn't refreshing even tho notifier is active
and if you need full code please have a look at this github link : https://github.com/Mishalhaneef/Note-app
Since this ValueNotifier has a type of List<NoteModel>, the value will not change when you add new items to the list or delete from it or clear all. The value here is a reference to the list which does not change.
You have to assign a new value to it, like:
noteListNotifier.value = List<NoteModel>[<add your current items here>];
You can manipulate your current list with List.from, removeWhere, add etc., and then re-assign the complete list.
Besides you don't need to call notifyListeners in case of a ValueNotifier, the framework handles it, see here.
Another approach would be to use a custom ChangeNotifierProvider where you can call notifyListeners when the contents of your list are changed.
Some further suggestions:
In your homescreen.dart file, instead of NoteDB.instance.noteListNotifier.value[index] you can use newNotes[index].
In data.dart, within getAllNotes, you have to set a new value for noteListNotifier in order to get the changes propagated. Currently you are just modifying items in this list and that is not considered to be a change. Try this code:
#override
Future<List<NoteModel>> getAllNotes() async {
//patching all data from local server using the url from [Post Man]
final _result = await dio.get(url.baseUrl+url.getAllNotes);
if (_result.data != null) {
//if the result data is not null the rest operation will be operate
//recived data's data decoding to json map
final _resultAsJsonMap = jsonDecode(_result.data);
//and that map converting to dart class and storing to another variable
final getNoteResponse = GetAllNotes.fromJson(_resultAsJsonMap);
noteListNotifier.value = getNoteResponse.data.reversed;
//and returning the class
return getNoteResponse.data;
} else {
noteListNotifier.value = <NoteModel>[];
return [];
}
}

Getx How to refresh list by using Obx

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(
...
...
));