I'm trying to read from Firebase (offline), but the method onDataChange is never called,
private class MyAuthStateListener implements Firebase.AuthStateListener {
#Override
public void onAuthStateChanged(AuthData authData) {
if (authData != null) {
// user is logged in
userId = authData.getUid();
mFirebase = mFirebase.child(authData.getUid()).child("xxx");
Query mQuery = mFirebase.orderByChild("externalId").equalTo(externalId);
mQuery.addListenerForSingleValueEvent(new MyValueEventListener());
}
}
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot mDataSnapshot) {
if (mDataSnapshot.hasChildren()) {
Iterable<DataSnapshot> i = mDataSnapshot.getChildren();
Iterator<DataSnapshot> mIterator = i.iterator();
if (mIterator.hasNext()) {
mArrayList.clear();
}
while (mIterator.hasNext()) {
DataSnapshot c = mIterator.next();
mArrayList.add(c.getValue(mObject.class));
}
}
onTaskComplete(); // call the notifyDataSetChange() on the adapter.
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
}
Have I done something wrong?
Online it works well, but offline it won't works.
The new objects are created with a FAB that open a new activity (to modify the object) and than I return to that activity.
Thank you all for the help.
Related
I have a list of different mines. Each mine has a list of blocks.
I have the mines in a spinner and the blocks in a recyclerview.
I want to display the different lists of blocks whenever the user changes the mine in the mine spinner
I am using Firebase in the backend as my database.
When I change the mine in the spinner, I update the block list by creating a new MutableLiveData which I've extended in a class called FirebaseQueryLiveData
The first time that I initialise the FirebaseQueryLiveData with the quesry containing the mine name, all the events inside it fire. However, after that, I call it and nothing fires. It breaks in the constructor if I have a breakpoint there, but it never reaches the run() method, onActive() method or the onDataChanged in the ValueEventListener.
I have done some research, and I have seen suggestions to replace the LiveData with MutableLiveData. I've done this, and it doesn't seem to make a difference.
Can anyone see anything in the code which I might be missing? I'm not very familiar with the android architecture components and I got the FirebaseQueryLiveData class from another helpful website with a tutorial, so I'm battling to understand where I have gone wrong.
I have done some research, and I have seen suggestions to replace the LiveData with MutableLiveData. I've done this, and it doesn't seem to make a difference.
public class BlockListActivityViewModel extends ViewModel {
private static DatabaseReference blockOutlineRef; // = FirebaseDatabase.getInstance().getReference(FireBasePaths.BLOCKOUTLINE.getPath("Therisa"));
private static DatabaseReference mineListRef;
private FirebaseQueryLiveData blockOutlineLiveDataQuery = null;
private LiveData<BlockOutlineList> blockOutlineLiveData = null;
private MediatorLiveData<String> selectedBlockNameMutableLiveData;
private MediatorLiveData<ArrayList<String>> mineListMutableLiveData;
public BlockListActivityViewModel() {
User loggedInUser = UserSingleton.getInstance();
setUpFirebasePersistance();
setupMineLiveData(loggedInUser);
// setupBlockOutlineListLiveData();
}
private void setupBlockOutlineListLiveData(String mineName) {
if (mineName != "") {
blockOutlineRef = FirebaseDatabase.getInstance().getReference(FireBasePaths.BLOCKOUTLINE.getPath(mineName));
blockOutlineLiveDataQuery = new FirebaseQueryLiveData(blockOutlineRef);
blockOutlineLiveData = Transformations.map(blockOutlineLiveDataQuery, new BlockOutlineHashMapDeserialiser());
}
}
private void setupMineLiveData(User user) {
ArrayList<String> mineNames = new ArrayList<>();
if (user != null) {
if (user.getWriteMines() != null) {
for (String mineName : user.getWriteMines().values()) {
mineNames.add(mineName);
}
}
}
setMineListMutableLiveData(mineNames);
if (mineNames.size() > 0) {
updateMineLiveData(mineNames.get(0));
}
}
public void updateMineLiveData(String mineName) {
SelectedMineSingleton.setMineName(mineName);
setupBlockOutlineListLiveData(SelectedMineSingleton.getInstance());
}
public void setUpFirebasePersistance() {
int i = 0;
// FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
private MutableLiveData<NamedBlockOutline> selectedBlockOutlineMutableLiveData;
public MutableLiveData<NamedBlockOutline> getSelectedBlockOutlineMutableLiveData() {
if (selectedBlockOutlineMutableLiveData == null) {
selectedBlockOutlineMutableLiveData = new MutableLiveData<>();
}
return selectedBlockOutlineMutableLiveData;
}
public void setSelectedBlockOutlineMutableLiveData(NamedBlockOutline namedBlockOutline) {
getSelectedBlockOutlineMutableLiveData().postValue(namedBlockOutline);
}
public MediatorLiveData<String> getSelectedBlockNameMutableLiveData() {
if (selectedBlockNameMutableLiveData == null)
selectedBlockNameMutableLiveData = new MediatorLiveData<>();
return selectedBlockNameMutableLiveData;
}
public void setSelectedBlockNameMutableLiveData(String blockName) {
selectedBlockNameMutableLiveData.postValue(blockName);
}
public MediatorLiveData<ArrayList<String>> getMineListMutableLiveData() {
if (mineListMutableLiveData == null)
mineListMutableLiveData = new MediatorLiveData<>();
return mineListMutableLiveData;
}
public void setMineListMutableLiveData(ArrayList<String> mineListString) {
getMineListMutableLiveData().postValue(mineListString);
}
private class BlockOutlineHashMapDeserialiser implements Function<DataSnapshot, BlockOutlineList>, android.arch.core.util.Function<DataSnapshot, BlockOutlineList> {
#Override
public BlockOutlineList apply(DataSnapshot dataSnapshot) {
BlockOutlineList blockOutlineList = new BlockOutlineList();
HashMap<String, NamedBlockOutline> blockOutlineStringHashMap = new HashMap<>();
for (DataSnapshot childData : dataSnapshot.getChildren()) {
NamedBlockOutline thisNamedOutline = new NamedBlockOutline();
HashMap<String, Object> blockOutlinePointHeader = (HashMap<String, Object>) childData.getValue();
HashMap<String, BlockPoint> blockOutlinePoints = (HashMap<String, BlockPoint>) blockOutlinePointHeader.get("blockOutlinePoints");
thisNamedOutline.setBlockName(childData.getKey());
thisNamedOutline.setBlockOutlinePoints(blockOutlinePoints);
blockOutlineStringHashMap.put(childData.getKey(), thisNamedOutline);
}
blockOutlineList.setBlockOutlineHashMap(blockOutlineStringHashMap);
return blockOutlineList;
}
}
#NonNull
public LiveData<BlockOutlineList> getBlockOutlineLiveData() {
return blockOutlineLiveData;
}
}
LiveData
public class FirebaseQueryLiveData extends MutableLiveData<DataSnapshot> {
private static final String LOG_TAG = "FirebaseQueryLiveData";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
private boolean listenerRemovePending = false;
private final Handler handler = new Handler();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
public FirebaseQueryLiveData(DatabaseReference ref) {
this.query = ref;
}
private final Runnable removeListener = new Runnable() {
#Override
public void run() {
query.removeEventListener(listener);
listenerRemovePending = false;
Log.d(LOG_TAG, "run");
}
};
#Override
protected void onActive() {
super.onActive();
if (listenerRemovePending) {
handler.removeCallbacks(removeListener);
Log.d(LOG_TAG, "listenerRemovePending");
}
else {
query.addValueEventListener(listener);
Log.d(LOG_TAG, "addValueEventListener");
}
listenerRemovePending = false;
Log.d(LOG_TAG, "listenerRemovePending");
}
#Override
protected void onInactive() {
super.onInactive();
// Listener removal is schedule on a two second delay
handler.postDelayed(removeListener, 4000);
listenerRemovePending = true;
Log.d(LOG_TAG, "listenerRemovePending");
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
I have a class that runs an asynchronous call to Firestore. I've implemented an interface and callback so I can get the data outside of the class. The problem I'm having is that when I minimize/reopen the activity the callback stops receiving data. I tested the Firestore call itself, and data is definitely being retrieved. It just seems that the callback stops passing data from the Firestore get() to the Activity.
Here's my class:
public class FirebaseGetBooks {
//firebase objects
private FirebaseFirestore mDbase;
private Activity activity;
private String groupID;
//default constructor
public FirebaseGetBooks() {
}
public FirebaseGetBooks(Activity activity) {
this.activity = activity;
//firebase new instances
mDbase = FirebaseFirestore.getInstance();
FirebaseGetGroupID firebaseGetGroupID = new FirebaseGetGroupID(activity);
groupID = firebaseGetGroupID.getGroupID();
}
public interface FirestoreCallback {
void onCallback(List<Book> books);
}
public void readDataRTUpdate(final FirestoreCallback firestoreCallback) {
mDbase.collection("books").whereEqualTo("groupID", groupID)
.addSnapshotListener(activity, new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
if (value != null) {
int i = 0;
List<Book> books = new ArrayList<>();
for (QueryDocumentSnapshot document : value) {
books.add(document.toObject(Book.class));
Log.d(TAG, "Book: " + books.get(i).toString());
i++;
}
firestoreCallback.onCallback(books);
Log.d(TAG, "Document updated.");
}
else {
Log.d(TAG, "No such document");
}
}
});
}
}
And here's my callback as seen in my activity:
public class MainActivity extends AppCompatActivity {
private FirebaseGetbook firebaseGetBooks;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
firebaseGetBooks = new FirebaseGetBooks(this);
firebaseGetBooks.readDataRTUpdate(new FirebaseGetBooks.FirestoreCallback() {
#Override
public void onCallback(List<Book> books) {
Log.d(TAG, "Books Still Firing: " + books.toString());
}
});
}
}
any help/insight would be greatly appreciated.
Thanks!
You are using the activity-scoped form of addSnapshotListener(). The listener is automatically removed when the onStop() method of the activity passed as the first parameter is called.
If you want the listener to remain active when the activity is in the background, remove activity from the call to addSnapshotListener(). Otherwise, move your call of firebaseGetBooks.readDataRTUpdate() from onCreate() to onStart().
I am following this tutorial to learn ViewModel and LiveData. In my case, instead of getting data from network, I am simply generating random string on button click and trying to update a textview. The problem is that the textview does not get updated when the data is changed by button click, but only gets updated when orientation is toggled.
Activity Class (extends LifecycleActivity)
public class PScreen extends BaseActivity {
#Override protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.places_screen);
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
viewModel.init();
viewModel.getUser().observe(this, new Observer<User>() {
#Override public void onChanged(#Nullable User user) {
((TextView) findViewById(R.id.name)).setText(user.getName());
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
final MutableLiveData<User> data = new MutableLiveData<>();
User user = new User();
user.setName(String.valueOf(Math.random() * 1000));
data.postValue(user);
viewModel.setUser(data); // Why it does not call observe()
}
});
}
}
ViewModel Class
package timsina.prabin.tripoptimizer.model;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
public class UserModel extends ViewModel {
private LiveData<User> user;
public void init() {
if (this.getUser() != null) {
return;
}
this.user = new LiveData<User>() {
#Override protected void setValue(User value) {
value.setName("Fresh New Name");
super.setValue(value);
}
};
}
public LiveData<User> getUser() {
return user;
}
public void setUser(LiveData<User> user) {
this.user = user;
}
}
You are creating a new LiveData instance each time! You are not supposed to do that. If you do that all previous observers will be ignored.
In this case you could replace your setUSer(LiveData<User>) method on your ViewModel to setUser(User u) (taking a User instead of a LiveData) and then do user.setValue(u) inside it.
Of course, will have to initialize the LiveData member in your ViewModel class, like this:
final private LiveData<User> user = new MutableLiveData<>();
It will work then because it will notify the existing observers.
I was somehow able to resolve this by using MutableLiveData instead of LiveData.
Model class
private MutableLiveData<User> user2;
public void init() {
if (user2 == null) {
user2 = new MutableLiveData<>();
}
}
public MutableLiveData<User> getUser2() {
return user2;
}
public void setUser2(final User user) {
user2.postValue(user);
}
Activity
viewModel.getUser2().observe(this, new Observer<User>() {
#Override public void onChanged(#Nullable User user) {
((TextView) findViewById(R.id.name)).setText(user.getName());
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
User user = new User();
viewModel.getUser().postValue(user);
}
});
You replace the reference to the object inside UserModel, try to swap the lines of code
data.postValue(user);
viewModel.setUser(data); // Why it does not call observe()
replace on
viewModel.setUser(data); // Why it does not call observe()
data.postValue(user);
Try to modify your code as #niqueco mentioned, set your updated method inside setUser() method and change your onclick() listener in the activity to send the new user data info only. Other works the LiveData will help u.
public class UserModel extends ViewModel {
private LiveData<User> user;
public void init() {
if (this.getUser() != null) {
return;
}
this.user = new LiveData<User>() {
#Override protected void setValue(User value) {
value.setName("Fresh New Name");
super.setValue(value);
}
};
}
public LiveData<User> getUser() {
return user;
}
public void setUser(LiveData<User> user) {
this.user.setValue(user); //the live data will help u push data
}
}
Activity Class
public class PScreen extends BaseActivity {
#Override protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.places_screen);
final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
viewModel.init();
viewModel.getUser().observe(this, new Observer<User>() {
#Override public void onChanged(#Nullable User user) {
((TextView) findViewById(R.id.name)).setText(user.getName());
}
});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
//final MutableLiveData<User> data = new MutableLiveData<>();
User user = new User();
user.setName(String.valueOf(Math.random() * 1000));
//data.postValue(user);
viewModel.setUser(user); // Why it does not call observe()
}
});
}
}
Firebase provides awesome Realtime Database service, but it is a bit outstanding from my, and I suggest that not only my, experience when this engine is used as main local storage (offline mode).
What do I expect when app is requesting some data is to see some indication of the process. But I have not found any direct functionality to get it working.
So, I created custom solution and would like to get reviews from community.
The idea is simple: bind a ValueEventListener to some version tag which creates a request with SingleValueEvent callback updating data and changing its state.
Suppose, i have a User object and for versioning I add ver property with timestamp content:
users : {
id : {
name : 'Mr. Anderson',
ver : '<timestamp>'
}
}
To see data state I introduce enum State, interface StateIndicatable, interface OnStateChangeListener:
enum State{ INIT, LOADING, LOADED, FAILED }
interface OnStateChangeListener{
void stateChanged(State newState);
}
interface StateIndicatable{
State getState();
void setOnStateChangeListener(OnStateChangeListener listener);
void clearOnStateChangeListener();
}
I wouldn't like to change the User model and I will use a wrapper which will exist on the level of view/presentation/view_model:
I'd like to create an abstract Wrapper around any versionable model:
abstract class FirebaseVersionedModelWrapper<M> implements StateIndicatable{
int version;
protected WeakReference<M> modelRef;
State state;
OnStateChangeListener onStateChangeListener;
DatabaseReference verRef;
FirebaseVersionedModelWrapper(M model, String firebasePath, OnStateChangeListener onStateChangeListener){
this.modelRef = new WeakReference(model);
this.state = State.INIT;
this.onStateChangeListener = onStateChangeListener;
verRef = FirebaseDatabase.getInstance().getReference(firebasePath + "/ver");
verRef.keepSynced(true);
verRef.addValueEventListener(
new OnValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(modelRef.get()==null){
verRef.removeEventListener(this);
return;
}
if(dataSnapshot.hasChild("ver"){
if(dataSnapshot.child("ver").getValue(Integer.class) != version){
requestUpdate();
}
}
else
setState(State.FAILED);
}
#Override
public void onCancelled(DatabaseError databaseError) {
if(modelRef.get()==null){
verRef.removeEventListener(this);
return;
}
setState(State.FAILED);
}
}
);
}
private void requestUpdate(){
setState(State.LOADING);
DatabaseReference ref = FirebaseDatabase
.getInstance
.getReference(firebasePath);
ref.keepSynced(true);
ref.addListenerForSingleValueEvent(
new OnValueEventListener(){
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
M model = modelRef.get();
if (model==null) return;
if(dataSnapshot.hasChild("ver")){
version = dataSnapshot.child("ver").getValue(Integer.class);
setFields(model, dataSnapshot);
setState(State.LOADED);
}
else{
setState(State.FAILED);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
setState(State.FAILED);
}
}
);
}
private void setState(State newState){
if (this.state == newState) return;
this.state = newState;
if (onStateChangeListener != null)
onStateChangeListener.stateChanged(newState);
}
#Override
public State getState(){
return this.state;
}
#Override
public void setOnStateChangeListener(OnStateChangeListener listener){
this.onStateChangeListener = listener;
}
#Override
public void clearOnStateChangeListener(){
this.onStateChangeListener = null;
}
protected abstract void setFields(M model, DataSnapshot dataSnapshot);
}
Here DatabaseReference#keepSynced(true) is called to be sure that the request on data path will return fresh data. This is discussable approach and not completely confirmed in my practice now.
Then I wrap the User into UserWrapper:
class UserWrapper extends FirebaseVersionedModelWrapper<User>{
FirebasedUser(User userModel, String userPath, OnStateChangeListener listener){
super(userModel, userPath, listener);
}
#Override
void setFields(User model, DataSnapshot dataSnapshot){
// setting values for model
}
}
And eventually I can instantiate the wrapper and bind indication routines to OnStateChangeListener callback:
User user = new User();
UserWrapper wrapper = new UserWrapper(
user,
new OnStateChangeListener(){
#Override
void stateChanged(State newState){
if(newState == State.LOADED){
showUserDetails();
}
else if(newState == State.FAILD){
showErrorState();
}
else{
showLoadingIndicator();
}
}
}
);
Well, as I said, any opinions and alternative solutions posted here are much appreciated!
the code is typed from memory and may have mistakes
I'm trying to update a RecyclerView with information from Firebase. I've successfully been able to update the TextViews in my view from Firebase but, in doing so, my current code won't update the getChildCount() of the RecyclerView with the amount of children from the database without infinitely looping.
My current code: (CardAdapter.java)
public int getChildCount() {
mDatabase = FirebaseDatabase.getInstance();
mReference = mDatabase.getReference();
final String userID = FirebaseAuth.getInstance().getCurrentUser().getUid();
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snap :
dataSnapshot.child("pet").child("users").child(userID).getChildren()) {
mChildCount = (int) snap.getChildrenCount();
}
notifyDataSetChanged();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mReference.addListenerForSingleValueEvent(listener);
return mChildCount;
}
This code works, but it infinitely loops due to the notifyDataSetChanged(); method that updates the RecyclerView.
What can I do to make it so the RecyclerView getChildCount() is dynamically updated from Firebase without infinitely looping?
You should not place your code to acquire data inside getChildrenCount(). I myself, usually place that in my Activity or inside constructor of Adapter. Like this:
public class YourAdapter extends RecyclerView.Adapter<YourAdapter.ViewHolder> {
private List<YourObject> yourObjectList;
public YourAdapter() {
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snap : dataSnapshot.child("pet").child("users").child(userID).getChildren()) {
...
// add to list
yourObjectList.add(snap.getValue(YourObject));
...
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mReference.addListenerForSingleValueEvent(listener);
...
}
#Override
public void onBindViewHolder(YourAdapter.ViewHolder holder, int position) {
YourObject yourObject = yourObjectList.get(position);
...
}
#Override
public int getItemCount() {
return yourObjectList.size();
}
}
Hope this help.