Flutter Firebase Database information not saving? - firebase

I'm creating the user Follows / Unfollows portion of my app and I'm coming up with several issues. The latest deals with following a fellow user and saving some reference info under a "follows" node. I'm looking at my Firebase console while doing this in my simulator and noticed the information appears correctly in green and 1 second later changes to red in order to delete. Here is how I'm saving things...
Future<Null> follow() async {
await fb
.child('users/${currentUser}/Follows/${friendsUID}')
.update({"name": userName, "photo": userImage});
}
I'm running the following code before my Widget build in order to determine if the user is being followed and which buttons should build accordingly....
void _friends() {
fb.child("users/${userid}/Follows/${id}").onValue.listen((Event event) {
setState(() {
// this will return true or false which is what you want based on your code :)
friends = event.snapshot.value == null;
});
});
}
And occasionally I'm receiving the following error message, I'm not sure if its related to saving the Firebase though...
setState() called after dispose(): FriendsVideoPageState#51581(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

Related

How to create UNDO button, flutter firebase

In my project I have a delete button that deletes some data from firebase firestore, what i'm trying to do is showing a snackbar with undo word, when it's pressed the process will be cancelled and no data will be deleted. I haven't found a solution yet. Is it possible?
You can use a Timer to create a callback that can be canceled anytime before the timer runs out.
import 'dart:async';
void main() {
final cancelable = Timer(Duration(seconds: 5), () {
// The delete function should be here
print('canceled after 3 seconds');
});
Timer(Duration(seconds: 3), cancelable.cancel);
}
In this example the print() callback gets canceled after three seconds, but you could pass the cancelable.cancel() function as a callback for the undo button and cancel the deletion on Firestore by clicking it before the Timer runs out
Deletes in Firestore are permanent; once you delete a document it is gone.
If you want to give the option to undo the deletion, you'll want to:
Either create the document in another collection before deleting it from the main one. For example, a common implementation is to have a subcollection with deleted documents, or even the entire history of each document.
Or you mark the documents as deleted, instead of really deleting them. This type of marking is typically referred to as a tombstone.

Firestore document update is failing

i have documents in firestore which i am trying to update. These updates are successful for old users but failing for new users. I have listview where user can see all the added documents. They click on Edit and then they are navigated to a detailed document view where they can make changes and save.
Error which i am receiving
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception:
[cloud_firestore/not-found] Some requested document was not found.
I am passing document id with following from list view to single document edit view;
data: docId
In the edit page, i am loading the user id and doc id which is coming from previous page.
class Edit extends StatefulWidget {
final DataModel data;
Edit({required this.data});
String ui = FirebaseAuth.instance.currentUser!.uid;
#override
_EditState createState() => _EditState();
}
On save button i am updating firestore with following;
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: () async {
DocumentReference edit = FirebaseFirestore
.instance
.collection('edit')
.doc(ui)
.collection('edit')
.doc(widget.data.documentId);
edit.update({
});
The cloud_firestore/not-found error means the document was not found under the specified id. Please make sure the id really exists in your database.
If you are using the local emulator, make sure you are accessing the correct database.
If you are using persistent storage (offline firestore data) it might be that it still has old data or your device didn't connect to the internet and thus doesn't have the current data.
The error message is telling you that the document you build a reference to was not found for update.
There are a few ways to update data to Cloud Firestore:
update - change fields on a document that already exists.
Fails if the document does not exist.
set - overwrite the data in a document, creating the
document if it does not already exist. If you want to only partially
update a document, use SetOptions.merge(). If you want to be able to
create a document if it doesn't exist, or update it if it does
already exist, you should pass SetOptions.merge as the second
parameter, as explained by the API documentation.
So you just need to use the correct operation for your use case.
If you want to update a document without knowing if it exists or not,
you want to use a set() with the merge() option like this (this
example is for Java):
// Update one field, creating the document if it does not already exist.
Map<String, Object> data = new HashMap<>();
data.put("capital", true);
db.collection("cities").document("BJ") .set(data, SetOptions.merge());
Also one classical problem when using auth.currentUser is that it is
possible that the Auth object is not fully initialized and that
auth.currentUser.uid is therefore null. As explained in the doc you
should either use the onAuthStateChanged() observer or check that
auth.currentUser is not null.
Try :
ElevatedButton.styleFrom(primary: Colors.redAccent),
onPressed: ()
async {
DocumentReference edit = FirebaseFirestore .instance .collection('edit') .doc(FirebaseAuth.instance.currentUser.uid) .collection('edit') .doc(widget.data.documentId);
edit.update({ });

Why does flutter firestore plugin not worry about closing its sinks (snapshot streams)?

In the flutter firestore codebase you can find a comment about the stream it creates when you run snapshots() on a query.
// It's fine to let the StreamController be garbage collected once all the
// subscribers have cancelled; this analyzer warning is safe to ignore.
StreamController<QuerySnapshotPlatform> controller; // ignore: close_sinks
I want to wrap my resulting snapshot streams with a BehaviorSubject so I can keep track of the latest entry. This is useful when I have one stream that is at the top of a page that I want to be consumed through different widgets farther down in my tree without reloading the stream each time. Without keeping track in a BehaviorSubject or elsewhere if a new widget starts listening to that stream it does not get the most recent information from Firestore as it missed that event.
Can I also not worry about closing the behavior subject I am going to create as it will be garbage collected when there are no more listeners? Or is there another way to achieve what I am wanting?
I'm picturing code like this:
final snapshotStream = _firestore.collection('users').snapshots();
final behaviorSubjectStream = BehaviorSubject();
behaviorSubjectStream.addStream(snapshotStream);
return behaviorSubjectStream;
This will get a complaint that I don't close the behaviorSubjectStream. Is it ok to ignore?
That depends on how you listen to the subject.
From what you describe, it sounds safe to ignore the hint. When the subscriptions that listen to the subject are cancelled, the subject will be cancelled as well (when the garbage collector finds it).
There are situations where you have a subscription that is still listening, but you want the subject to stop emitting. In that case you will need to close() the subject.
You can test that the subject is correctly cancelled by adding
behaviorSubjectStream.onCancel = () {
print("onCancel");
};
Then you can test it by playing around with your app.

Remove item by name in Firebase React-Native app

I am trying to remove/delete items from my Firebase Database onPress of a button. The following is my code for the method I'm using to attempt to remove the item.
let itemsRef = db.ref('/items');
handleRemove = (item) => {
itemsRef.remove({
name: item
});
}
My method for remove is the same implementation of the push method that adds items to the database.
I get an error stating that - "Error: Reference.remove failed: first argument must be a valid function"
See image of error screen
The remove method doesnt take an object as a parameter. You should only do this:
itemsRef.remove();
From the docs:
remove
remove(onComplete?: function): Promise
Removes the data at this Database location.
Any data at child locations will also be deleted.
The effect of the remove will be visible immediately and the corresponding event 'value' will be triggered. Synchronization of the remove to the Firebase servers will also be started, and the returned Promise will resolve when complete. If provided, the onComplete callback will be called asynchronously after synchronization has finished

Autofac SingleInstance() and Xamarin Forms

To start, let me say that I have read several questions here about SingleInstance, but still cannot find a direct answer that helps me. That said, I apologize if I missed anything.
Here's my question:
I am building a Xamarin Forms app for iOS and Android. I have a single AppInitializer class in a PCL where I register all of my interface dependencies using Autofac. I then assign the Container from the builder as a static property on the app class. The problem I encounter is that while I'm registering everything with .SingleInstance(), I'm not actually getting a single instance.
Init Logic Example:
var builder = new ContainerBuilder();
builder.RegisterType<ErrorHandler>().SingleInstance().As<IErrorHandler>();
…
builder.RegisterType<MemberViewModel>().SingleInstance().As<IMemberViewModel>();
…
AppContainer.Current = builder.Build();
I am letting Autofac handle resolving interfaces in my constructors. For example:
public MemberViewModel(ISettingsViewModel settings, IErrorHandler errorHandler, …) : base(settings, errorHandler){…}
I then use said model on a page as below:
Example page usage:
public ProfilePage()
{
InitializeComponent();
var displayModel = Model.CurrentMember;
…
}
…
**public IMemberViewModel Model =>
AppContainer.Current.Resolve<IMemberViewModel>();**
In this example I set Model.CurrentMember's properties immediately before arriving on this page. I've set breakpoints and know for a fact this is happening. However, when I resolve the instance of the model, the properties on CurrentMember are null.
Am I doing something wrong here or have I encountered a bug?
-Edit-
Made it clear that I'm using Autofac.
-Edit 2-
Adding more detail.
My implementation of the IMemberViewModel class has various properties on it, including an observable object called current member. It is declared as below:
public class MemberViewModel : ViewModelBase, IMemberViewModel
{
…
(see constructor above)
…
public MemberDisplay CurrentMember =>
m_CurrentMember ?? (m_CurrentMember = new MemberDisplay())
On the implementation of IMemberViewModel I have a method that sets the various properties on CurrentMember.
The order of operations is this:
The end user taps an image for a member. This fires a command on the (theoretically) singleton instance of the IMemberViewModel implementation. This command executes an async task that awaits an async call to the API to load the data for that member. After that data is loaded and the properties set on CurrentMember, the app navigates to the profile screen. The profile screen resolves IMemberViewModel (per above).
Expected Behavior:
The properties on CurrentMember from the resolved instance of IMemberViewModel are set to the values that have just been set from the load data method. This expectation arises from assuming that there is a single instance of IMemberViewModel.
Actual Behavior:
The CurrentMember's properties are at their default values, i.e. string.Empty, 0, null, etc.
The odd thing here is that this doesn't happen to every model. I have a message model that I am resolving in the same manner on the same screen and it seems to be fine.
This issue turned out to be caused by the way we were going about initializing everything. For posterity's sake, I will give a brief breakdown of what was happening and what I did to prevent it.
Previous App Flow:
App opens & constructor is called. This calls into the initialization routine above.
User logs in.
First instance of IMemberViewModel resolved using static container.
A message pops up asking the user for Push Notifications Permissions
When this happens, the app OnSleep is called (iOS)
After the user selects an answer, OnResume is called.
OnResume calls initialization routine
New container created.
Call to load data happens on old container, new pages reference new container.
Issue arises as described above.
Correction to the flow:
First, from what I can tell the init calls do not need to be made on resume and/or start if made in the app constructor. If the app is "killed" because other apps need the memory space, a fresh version of the app will be created on next launch (see the Android Activity Lifecycle and the iOS App Lifecycle).
Second, because I'm paranoid and because it can't hurt, in the app init routine I am now checking to determine whether the container exists and whether the interface is already registered.
public static void Init(ISetup setup)
{
if (Container != null && IsModelRegistered()) return;
RegisterDependencies(setup);
…
}
private static bool IsModelRegistered()
{
return Container.IsRegistered<IMemberViewModel>();
}

Resources