Flutter - how can i update the data to the realtime datebase on firebase and find those keys if i dont know it - flutter

I am new to flutter, I am wondering how I can update the data in realtime datebase on firebase if i dont know those keys.
For example, I want to change the partStatus from "In progress" to "delivered" either dynamically or individually when i dont know those keys.

You have to make sure that whenever you add a new item to your database and autoId has been generated, the database-generated-id should be the id of the model class you use to hold the data in your project. So whenever an update is made, referencing the id as database key can be able to get you to the product you want to update.
See the below sample code:
...
//fetching logic here
Future<void> fetchAndSetOrders() async {
final url = 'https://<*firebase-project-name*>.firebaseio.com/orderList.json?auth=$authToken';
//add try and catch errors
try {
final response = await http.get(url);
final responseData = json.decode(response.body) as Map<String, dynamic>;
if (responseData == null) {
return;
}
List<OrderItem> loadedOrders = [];
responseData.forEach(
(orderId, orderData) => loadedOrders.add(
OrderItem(
orderNo: orderData['orderNo'],
partNo: orderData['partNo'],
status: orderData['partStatus'],
qty: orderData['qty'],
//**here id of order item is the orderId attached to the response
id: orderId,
),
),
);
_items = loadedOrders;
notifyListeners();
} catch (error) {
//
}
}
...
Now to update the order.. since each order has an id equal to the key of the dataitem in the database
...
Future<void> updateOrder(String orderId, OrderItem order) async {
//note that i added the orderId to the url string
final url = 'https://<*firebase-project-name*>.firebaseio.com/orderList/$orderId.json?auth=$authToken';
await http.patch(
url,
body: json.encode({
'orderNo': order.orderNo,
'partNo': order.partNo,
'partStatus': order.status,
'qty': order.qty,
}),
);
}
...
Happy Coding D:)

Related

How to fetch data and update it from firebase

I am having trouble trying to fetch data from firebase and updating the values from it.
I have a restaurant name and the number of times it has been picked (user chooses to go to that restaurant to eat). I am trying to retrieve the numPicked and update it by adding one if the user decides to go there again.
Here i am trying to fetch ONE specific document and trying to store the docID and the variables I need to update.
docID = doc.id; docID is return NULL
meaning that the foreach loop isn't even being read.
Future<bool> searchQuery(
{required String restaurantName,
required var userID,
required db}) async {
int addOne = 1; //addes one if it has been picked
//this is not working
try {
Query query2 =
db.where('userId', isEqualTo: FirebaseAuth.instance.currentUser!.uid);
Query query = query2.where('restaurantName', isEqualTo: restaurantName);
await query.get().then((querySnapshot) {
// ignore: avoid_function_literals_in_foreach_calls
querySnapshot.docs.forEach((doc) {
docID = doc.id;
numPicked = doc['numPicked'];
restaurantExist = true;
});
}).catchError((error) {
// print('error querying: #error');
});
} catch (ex) {
// ignore: avoid_print
print(ex);
}
//this is not working
int totalPicked = numPicked + addOne;
//if the restaurant exist then update the numpicked for that specific restaurant
if (restaurantExist) {
try {
var query = db
//.collection('NumRestaurantPicked')
.doc(docID);
await query.update({'numPicked': totalPicked.toString()});
} catch (ex) {}
}
return restaurantExist;
}
The docID and numPicked variables are not defined in the method signature, so they are not accessible outside of the try block. They should be defined as class variables, so they can be accessed from other methods.

How to pass data from one table to another table using flutter (get and post on 2 different tables)?

I am creating an ecommerce Android flutter application, and I am new to this dart language. I need to get data from one table and post it to another table, where the API is built in .NET Core using a SQL Server database.
This is my code:
httpService.getPosts().then((value) {
if (value != null) {
value.forEach((element) {
httpServices.addPosts(
0,
element.cartProductID, element.productBrandId,
element.cartUserID, element.item,
element.quantity, element.price,
element.totalPrice,
element.discount,
// element.isOrdered,
element.paymentID,
element.paymentMode,
element.date,
);
});
My get method
class GetOrderHttpService with ChangeNotifier {
Future<List<OrderTotal>> getPosts() async {
Response res =
await http.get(Uri.https('********'));
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<OrderTotal> posts = body
.map(
(dynamic dynamic) => OrderTotal.fromJson(dynamic),
)
.toList();
notifyListeners();
return posts;
} else {
throw "Unable to retrieve posts.";
}
}
}
Future<bool> addPosts(
int orderID,
int orderProductID,
int productBrandId,
int orderUserID,
String item,
int quantity,
double price,
double totalPrice,
double discount,
int paymentID,
String? paymentMode,
DateTime date,
) async {
var response = await http.post(
Uri.https('************'),
body: jsonEncode({
'orderID': orderID,
'orderProductID': orderProductID,
'productBrandId': productBrandId,
'orderUserID': orderUserID,
'item': item,
'quantity': quantity,
'price': price,
'totalPrice': totalPrice,
'discount': discount,
'paymentID': paymentID,
'paymentMode': paymentMode,
'date': date
}),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
var data = response.body;
if (response.statusCode == 200) {
return true;
} else
throw Exception();
}
}
It successfully retrieves the data and passes it on to the future post method, but the database is not updated. When the breakpoint hits the post method, it doesn't go through the code and doesn't get any status code. Thank you
Notice you are using Future in both your get() and post() methods, but, when calling these methods you are not using the "await" keyword. You should use it every time you call a Future function assuring you are waiting that method to complete and retrieve data successfully. It might work without it (as you say your get method works) but, in more complex situations this might not be the case due to asynchronous nature of these type of functions.
Your code should look like this:
await httpService.getPosts().then((value) async {
if (value != null) {
value.forEach((element) {
await httpServices.addPosts(
0,
element.cartProductID, element.productBrandId,
element.cartUserID, element.item,
element.quantity, element.price,
element.totalPrice,
element.discount,
// element.isOrdered,
element.paymentID,
element.paymentMode,
element.date,
);
});
Hope this works. Have a nice day!

Importing an SQFlite database from Flutter app's assets and using rawQuery to display specific rows

I've built an app using Flutter. Part of its functionality is that users can search through data which is in the assets area of the app. This data was originally in JSON format, although I have converted it into an SQLite database to save storage space. That has actually helped me to save around 90%, which is great. The problem is, the search delegate no longer works. It simply returns an empty list, although no errors are produced in the console.
I have created a model class to help read the data from the SQLite database table, which looks like this:
/// Class to handle the country data in the database
class CountriesDB {
/// Defining the variables to be pulled from the json file
late int id;
late String continent;
late String continentISO;
late String country;
late String countryISO;
late String flagIconLocation;
CountriesDB({
required this.id,
required this.continent,
required this.continentISO,
required this.country,
required this.countryISO,
required this.flagIconLocation,
});
CountriesDB.fromMap(dynamic obj) {
this.id = obj[id];
this.continent = obj[continent];
this.continentISO = obj[continentISO];
this.country = obj[country];
this.countryISO = obj[countryISO];
this.flagIconLocation = obj[flagIconLocation];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'id': id,
'continent': continent,
'continentISO': continentISO,
'country': country,
'countryISO': countryISO,
'flagIconLocation': flagIconLocation,
};
return map;
}
}
As far as I am aware, to read data in a database that is stored within the assets folder of the app, I need to programatically convert it into a working database. I have written the following code, to sort that:
/// Creating the database values
static final DatabaseClientData instance = DatabaseClientData._init();
static Database? _database;
DatabaseClientData._init();
/// Calling the database
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('databaseWorking.db');
return _database!;
}
/// Future function to open the database
Future<Database> _initDB(String filePath) async {
/// Getting the data from the database in 'assets'
var databasesPath = await getDatabasesPath();
var path = join(databasesPath, filePath);
/// Check if the database exists
var exists = await databaseExists(path);
if (!exists) {
/// Should happen only the first time the application is launched
print('Creating new copy from asset');
/// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
/// Copy from the asset
ByteData data =
await rootBundle.load('assets/data/database.db');
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
/// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print('Opening existing database');
}
return await openDatabase(path, readOnly: true);
}
The next thing I have done is to create a Future function that searches the database using a rawQuery. The code for this is:
/// Functions to search for specific database entries
/// Countries
static Future<List<CountriesDB>> searchCountries(String keyword) async {
final db = await instance.database;
List<Map<String, dynamic>> allCountries = await db.rawQuery(
'SELECT * FROM availableISOCountries WHERE continent=? OR continentISO=? OR country=? OR countryISO=?',
['%keyword%']);
List<CountriesDB> countries =
allCountries.map((country) => CountriesDB.fromMap(country)).toList();
return countries;
}
Finally, I am using the Flutter Search Delegate class to allow the user to interact with the database and search for specific rows. This is the widget I have built for that:
/// Checks to see if suggestions can be made and returns error if not
Widget buildSuggestions(BuildContext context) => Container(
color: Color(0xFFF7F7F7),
child: FutureBuilder<List<CountriesDB>>(
future: DatabaseClientData.searchCountries(query),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: PlatformCircularProgressIndicator(
material: (_, __) => MaterialProgressIndicatorData(
color: Color(0xFF287AD3),
),
cupertino: (_, __) => CupertinoProgressIndicatorData(),
));
default:
if (query.isEmpty) {
return buildAllSuggestionsNoSearch(snapshot.data!);
} else if (snapshot.hasError || snapshot.data!.isEmpty) {
return buildNoSuggestionsError(context);
} else {
return buildSuggestionsSuccess(snapshot.data!);
}
}
},
),
);
The idea is that the functionality I have built will return the whole list before a user searches and once a users starts typing, they will only be shown any rows that match their search query. This worked fine when I was using JSON data but it is returning an empty list, yet there are no errors printed in the console, at all. That makes it quite hard to know where my code is going wrong.
Where have I gone wrong with my code, such that it is not returning any data? How can I correct this? Thanks!

Does streambuilder from firebase rtdb will update list<User> user data?

currently I understadn with the method streamBuilder I can fetch updated data and add in the List<User> users.
But what if this user which is already added in the List<User> users has updated data, and then it could be double adding this user data in the List<User> users right?
Could you plz show me how to confirm whether for the new coming data List<User> users has already same userId, if yes, the new data / userId will replace this exisiting userId?
If the user is deleted from Firebase rtdb, the stream will be notified, and therefore remove this user from List<User> users?
here is example, my concern is since stream will always add data to the List users, but what if this user is removed from database or disconnect, how to remove this user from this list?
_streamSubscription = availableUserStream.onValue.listen((snap) {
if (snap.snapshot.exists && snap.snapshot.value != null) {
DataSnapshot snapshotData = snap.snapshot;
for (var userSnapshot in snapshotData.children) {
final data = Map<String, dynamic>.from(userSnapshot.value as Map);
List<User> users = [];
User newUser = User.fromJson(data);
users.add(newUser);
firebaseController.setUsers(users: users);
}
}
});
So I thought to do a double confirm here if this user is still exisitng in the database:
User getRandomSenderUser({User asReceiverUser}) {
if (availableSenderUsersList.isNotEmpty) {
final random = Random();
var i = random.nextInt(availableSenderUsersList.length);
User randomUser = availableSenderUsersList[i];
bool thisRandomUserIsAvailable; //TODO
I don't know how to do this check, e.g. if this randomerUser is unavailable, so I need to get next randomUser, so it should be a loop? But it will slow down the response speed.
updateSenderUserAvailableStatus(asReceiverUser:asReceiverUser,connectionUser: randomUser);
return randomUser;
} else {
return null;
}
}
thank you!
Update:
Here is the example code, so now I understand stream will pass user data to List<User> users, but in my way there will always be user who is added in this list before, but was already removed from database, my plan is using while loop for double confirming to remove unavailable user when getting the randomUser, but it sounds not smart and still waste time I guess....
#override
void initState() {
_listenAvailableUsers();
}
_listenAvailableUsers() {
var availableUserStream =
FirebaseDatabase.instance.ref().child('/waitingList');
_streamSubscription = availableUserStream.onValue.listen((snap) {
if (snap.snapshot.exists && snap.snapshot.value != null) {
DataSnapshot snapshotData = snap.snapshot;
for (var userSnapshot in snapshotData.children) {
final data = Map<String, dynamic>.from(userSnapshot.value as Map);
List<User> users = [];
User newUser = User.fromJson(data);
users.add(newUser);
firebaseController.setUsers(users: users);
}
}
});
}
Here is the method I though to confirm if the randomUser is still existing in the database:
Future<User> getRandomSenderUser({User asReceiverUser}) async {
if (availableSenderUsersList.isNotEmpty) {
User randomUser;
while (true) {
final random = Random();
var i = random.nextInt(availableSenderUsersList.length);
randomUser = availableSenderUsersList[i];
DatabaseEvent event = await databaseReference
.child('/waitingList/${randomUser.userId}')
.once();
print('randomUser is ${randomUser.toString()}');
if (event.snapshot.value != null) {
break;
}
}
await updateSenderUserAvailableStatus(
asReceiverUser: asReceiverUser, connectionUser: randomUser);
print('connectionUserId is $connectionUserId');
return randomUser;
} else {
return null;
}
}
Since you're listening to the onValue of a path in the database, the DataSnapshot you get will contain the entire data at that path. When there was only a small change in the data, the server will only send that update to the client, but the SDK will then merge that with the existing data and still fire an event with a snapshot of all the data at the path.
Since you're starting with an empty list (List<User> users = [];) each time you get an event from the stream, that means you're rebuilding the entire lit of users each time, which seems correct to me.

Unable to use a Future value - Flutter/Dart

I've fetched a json object and deserialized it and then returned it too.
I want to use this in another file.
I'm unable to assign the values that I'm getting in the first step.
Here are all the codes...
Service
Future getGeoPoints(String accessToken, String tripId) async {
String requestUrl;
var response = await get(
Uri.parse(requestUrl),
headers: {
'Authorization': "Bearer $accessToken",
},
);
if (response.statusCode == 200) {
Map<String, dynamic> responseBody = json.decode(response.body);
GetGeoPoints geoPoints = GetGeoPoints.fromJson(responseBody);
List listOfGeoPoints = [];
for (var geoPoint in geoPoints.geoPoints) {
listOfGeoPoints.add(
{
'latitude': geoPoint.latitude,
'longitude': geoPoint.longitude,
'timestamp': geoPoint.timeStamp,
},
);
}
// print('List of geo points: ' + '$listOfGeoPoints');
return listOfGeoPoints;
} else {
throw Exception('Failed to load data from server');
}
}
File where I need the above values
List routeCoordinates;
Future<void> getValues() async {
getGeoPoints(widget.accessToken, widget.tripId)
.then((value) => routeCoordinates = value);
}
When I run the app, routeCoordinates is null but when I hotreload, it contains the value.
I want to have the values as soon as the screen starts. What is the right way to assign the values here?
I've also tried this:
routeCoordinates = getGeoPoints...
It throws error..
Please help.. Thanks..
The function getGeoPoints() is an asynchronous one. But on the other file, you are not using the await keyword, instead you are using then(). So your code is not waiting for that function to return value.
Try using below code,
List routeCoordinates;
Future<void> getValues() async {
routeCoordinates = await getGeoPoints(widget.accessToken, widget.tripId);
}
Let us know how it went.
You need to use a FutureBuilder to define a behaviour depending on the state of the request. You'll be able to tell the widget what to return while your app is waiting for the response to your request. You can also return a specific widget if you get an error(if your user is offline, for example).
Edit: I've linked the official docs but give this article a read if it's not clear enough.