I'm a long-time follower, first-time poster, so I hope I get this right. I'm trying to get into android-firebase development, and figured that the best way for that was to pick a project and try to find my way. I've been trying to develop a simple app for work (I'm a doctor) that'll allow our team to view brief progress notes about our in-patients, and will also allow us to post tasks needed to be performed on each patient. I've gotten most of it working, however, I'm stuck at one point. After I open a patient's record, A detail page loads, that includes a "comments" section, and a "tasks" section. Both of these are RecyclerViews containing many-to-one entries. The problem I'm facing has to do with trying to delete one of the "tasks" items when it's clicked (so it's removed from the list, and is no longer pending). I keep getting a Null Pointer Exception. The problem is, I'm not entirely sure if my code is correct. I'm still learning, so my code includes a lot of sections from different places that I've played with(I started with the android-firebase database sample from github, along with many other online sources), and re-written (after a lot of breaking and fixing) to get it working. Sorry for the long introduction. So, here's what I've got so far (not the complete code, just the sections I think are significant). I hope someone could help me out.
postDetailActivity.java
private static class TaskViewHolder extends RecyclerView.ViewHolder {
public TextView dateView;
public TextView bodyView;
public ImageButton checkView;
public TaskViewHolder(View itemView, final TaskAdapter mTaskAdapter) {
super(itemView);
dateView = (TextView) itemView.findViewById(R.id.task_date);
bodyView = (TextView) itemView.findViewById(R.id.task_body);
checkView = (ImageButton) itemView.findViewById(R.id.check);
checkView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Adapter Position:" + getAdapterPosition());
mTaskAdapter.removeItem(getAdapterPosition());
}
});
}
private static class TaskAdapter extends RecyclerView.Adapter<TaskViewHolder> {
private Context mContext;
private DatabaseReference mDatabaseReference;
private DatabaseReference mTaskReference;
private ChildEventListener mChildEventListener;
private FirebaseRecyclerAdapter<Task, TaskViewHolder> mAdapter;
private List<String> mTaskIds = new ArrayList<>();
private List<Task> mTasks = new ArrayList<>();
public TaskAdapter(final Context context, DatabaseReference ref) {
mContext = context;
mDatabaseReference = ref;
// Create child event listener
// [START child_event_listener_recycler]
ChildEventListener childEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
// A new task has been added, add it to the displayed list
Task task = dataSnapshot.getValue(Task.class);
// [START_EXCLUDE]
// Update RecyclerView
mTaskIds.add(dataSnapshot.getKey());
mTasks.add(task);
notifyItemInserted(mTasks.size() - 1);
// [END_EXCLUDE]
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());
// A task has changed, use the key to determine if we are displaying this
// task and if so displayed the changed task.
Task newTask = dataSnapshot.getValue(Task.class);
String taskKey = dataSnapshot.getKey();
// [START_EXCLUDE]
int taskIndex = mTaskIds.indexOf(taskKey);
if (taskIndex > -1) {
// Replace with the new data
mTasks.set(taskIndex, newTask);
// Update the RecyclerView
notifyItemChanged(taskIndex);
} else {
Log.w(TAG, "onChildChanged:unknown_child:" + taskKey);
}
// [END_EXCLUDE]
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());
// A task has changed, use the key to determine if we are displaying this
// task and if so remove it.
String taskKey = dataSnapshot.getKey();
// [START_EXCLUDE]
int taskIndex = mTaskIds.indexOf(taskKey);
if (taskIndex > -1) {
// Remove data from the list
mTaskIds.remove(taskIndex);
mTasks.remove(taskIndex);
// Update the RecyclerView
notifyItemRemoved(taskIndex);
} else {
Log.w(TAG, "onChildRemoved:unknown_child:" + taskKey);
}
// [END_EXCLUDE]
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());
// A task has changed position, use the key to determine if we are
// displaying this task and if so move it.
Task movedTask = dataSnapshot.getValue(Task.class);
String taskKey = dataSnapshot.getKey();
// ...
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "postTasks:onCancelled", databaseError.toException());
Toast.makeText(mContext, "Failed to load tasks.",
Toast.LENGTH_SHORT).show();
}
};
ref.addChildEventListener(childEventListener);
// [END child_event_listener_recycler]
// Store reference to listener so it can be removed on app stop
mChildEventListener = childEventListener;
}
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.item_task, parent, false);
return new TaskViewHolder(view, this);
}
#Override
public void onBindViewHolder(TaskViewHolder holder, int position) {
Task task = mTasks.get(position);
holder.dateView.setText(task.date);
holder.bodyView.setText(task.text);
}
#Override
public int getItemCount() {
return mTasks.size();
}
public void cleanupListener() {
if (mChildEventListener != null) {
mDatabaseReference.removeEventListener(mChildEventListener);
}
}
public void removeItem(int position) {
mAdapter.getRef(position).removeValue();
mAdapter.notifyItemRemoved(position);
Error Log
java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.firebase.database.DatabaseReference com.firebase.ui.database.FirebaseRecyclerAdapter.getRef(int)' on a null object reference
at com.hanykasem.omfsrounds.PostDetailActivity$TaskAdapter.removeItem(PostDetailActivity.java:606)
at com.hanykasem.omfsrounds.PostDetailActivity$TaskViewHolder$1.onClick(PostDetailActivity.java:451)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Like I said, I'm not really sure where the problem is, and I've tried a lot to get it sorted out, but couldn't. Any help would be highly appreciated.
Thank you very much.
I am not expert in firebase, but from your code I do not see are you assigning anything to your mAdapter field. That is why you are having NPE later on when accessing it in your removeItem method.
Example on how firebase adapter can be used:
http://www.coderefer.com/firebaseui-android-firebase-database/
Related
I have an Android app which uses firestore as its database. I have followed this series of blog posts to set up my firestore database in my app : https://firebase.googleblog.com/2017/12/using-android-architecture-components.html and then followed this stackoverflow entry to change my code to work for firestore: Android Architecture Components with Firebase specifically Firestore.
After this I was successful to display the result of my query in a recycler view, however when I added the swap to update (I do soft delete by setting a isActive flag to false) action in my app, LiveData was inconsistent in refreshing the RecyclerView. Here is my code snippets:
MainActivity.java
TaskViewModel viewModel =
ViewModelProviders.of(this).get(TaskViewModel.class);
LiveData<LinkedList<TaskProperties>> liveData = viewModel.getTaskPropertiesLiveData();
final MainActivity mainActivityReference = this;
liveData.observe(this, new Observer<LinkedList<TaskProperties>>() {
#Override
public void onChanged(#Nullable LinkedList<TaskProperties> taskProperties) {
if (taskProperties != null) {
// Get a handle to the RecyclerView.
mRecyclerView = findViewById(R.id.recyclerview);
// Create an adapter and supply the data to be displayed.
mAdapter = new TaskListAdapter(mainActivityReference, taskProperties);
// Connect the adapter with the RecyclerView.
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(mAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(mRecyclerView);
mRecyclerView.setAdapter(mAdapter);
// Give the RecyclerView a default layout manager.
mRecyclerView.setLayoutManager(new LinearLayoutManager(mainActivityReference));
}
}
});
View Model:
public class TaskViewModel extends ViewModel {
private LinkedList<TaskProperties> taskProperties;
private static final Query PROJECT_REF = FirebaseFirestore.getInstance().collection("project").whereEqualTo("active", true);
private final FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(PROJECT_REF);
public TaskViewModel() {
taskPropertiesLiveData.addSource(liveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable final QuerySnapshot querySnapshot) {
if (querySnapshot != null) {
new Thread(new Runnable() {
#Override
public void run() {
taskProperties = new LinkedList<TaskProperties>();
for (DocumentSnapshot document : querySnapshot.getDocuments()) {
taskProperties.addLast(document.toObject(TaskProperties.class));
}
taskPropertiesLiveData.postValue(taskProperties);
}
}).start();
} else {
taskPropertiesLiveData.setValue(null);
}
}
});
}
#NonNull
public LiveData<LinkedList<TaskProperties>> getTaskPropertiesLiveData() {
return taskPropertiesLiveData;
}
}
Code in the callback class to remove :
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
Constructor in Adapter:-
public TaskListAdapter(Context context,LinkedList<TaskProperties> taskList) {
mInflater = LayoutInflater.from(context);
this.taskList = taskList;
}
Code in Adapter to remove:-
public void onItemDismiss(int position) {
TaskDao taskDao = new TaskDao();
taskDao.softDeleteTaskInDB(taskList.get(position));
}
Code in DAO class to update( soft delete) :-
public void softDeleteTaskInDB(TaskProperties taskProperties){
taskProperties.setActive(false);
database.collection("project")
.document(taskProperties.getTask())
.set(taskProperties).
addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(DEBUG_TAG, "DocumentSnapshot successfully written!");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(DEBUG_TAG, "Error writing document", e);
}
});
Log.i(DEBUG_TAG,taskProperties.getTask());
}
I have observed that LiveData was able to refresh the view when I was deleting one component from the end of the list, however when I deleted from the middle of the list the view sometimes does not refresh properly. From the logs I found that the position that is being passed into the adapter class is working fine, however the tasklist array does not have the most updated value.
For example if the task list contains :-
Cat
Dog
Mouse
Rabbit
Tiger
and if delete Mouse and then Rabbit in quick succession, the onItemDismiss in adapter class receives position 3 in both cases, but the taskList variable in the Adapter class still contains Mouse at position 3. This means the LiveData might not have refreshed the RecyclerView.
Can someone please tell me where am I going wrong?
Thanks,
Sangho
As refer in this link. I want to try in my coding apps. But it didnt retrieve any data from my firebase database. I just cant figure out how to fix it. Can someone please help me. Is there any idea, where did I miss?
My firebase database as show image below:-
Spinner coding:-
mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.keepSynced(true);
mDatabase.child("Advertisement").child(mAuth.getUid()).addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
//final List<String> name = new ArrayList<String>();
ArrayList<String> identity = new ArrayList<>();
identity.add(0, "Choose Tuition Centre");
for (DataSnapshot nameSnapshot: dataSnapshot.getChildren())
{
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class);
identity.add(tuitionName);
}
ArrayAdapter<String> nameAdapter = new ArrayAdapter<String>(RatingActivity.this, android.R.layout.simple_spinner_item, identity);
nameAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mName.setAdapter(nameAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mName.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if(position==0)
{
//Toast.makeText(getApplicationContext(),"No Item Selected",Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(getApplicationContext(),parent.getItemAtPosition(position) +" Selected",Toast.LENGTH_SHORT).show();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent)
{
// TODO Auto-generated method stub
}
});
Add listener as below :
DatabaseReference mDatabaseRef =
FirebaseDatabase.getInstance().getReference();
ArrayList<String> list=new ArrayList<>();
mDatabaseRef.child("Advertisement")
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot :dataSnapshot.getChildren())
{
Advertisementject advertisement=
snapshot.getValue(Advertisement.class);
//replace Object.class with your class name
//get your key value here from your "Custom class"
// which contains "adstutioname"
// add in list
list.add(advertisement.getAdstutuioname());
}
//set adapter
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
generate class like this
public class Advertisement{
#SerializedName("adstutuioname")
#Expose
private String adstutuioname;
public String getAdstutuioname() {
return adstutuioname;
}
public void setAdstutuioname(String adstutuioname) {
this.adstutuioname = adstutuioname;
}
}
Your class contains all your params and .
Replace Object with this class
I hope the problem is with your getValue() method. Firebase getValue method returns an Object, and you are expecting a String. Please modify this line to convert your Object to String -
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class).toString();
It's not clear if you're trying to read once, or watch changes Difference between addValueEventListener() and addListenerForSingleValueEvent() of firebase
First things first, look if you are getting a database error
#Override
public void onCancelled(DatabaseError databaseError) {
// Toast or print the databaseError, don't ignore it
}
Then look where you're adding a listener - mDatabase.child("Advertisement").child(mAuth.getUid())., which from your picture is every element at the level of -LNZ6....
For that level, you have a list of object's, so you can need to use a loop, and as shown from some examples on Firebase site, it's recommended that you make an object, but you still can use child method for a certain field as you're trying to do
But if a field doesn't exist, you get null value, and Adapters cannot hold nulls, so you must ignore or set some default value.
ArrayList<String> identity = new ArrayList<>();
identity.add(0, "Choose Tuition Centre");
for (DataSnapshot nameSnapshot: dataSnapshot.getChildren()) {
String tuitionName = nameSnapshot.child("adstuitioname").getValue(String.class);
Log.d("DEBUG TUITION", String.valueOf(tuitionName); // look for this in your logs
identity.add(tuitionName == null ? "none" : tuitionName);
}
ArrayAdapter<String> nameAdapter =...
I've a RecyclerView adapter as
public class AllPostAdapter extends RecyclerView.Adapter<PostHolder> {
private AppCompatActivity mContext;
private List<Post> postList;
public AllPostAdapter(AppCompatActivity mContext, List<Post> postList){
this.mContext = mContext;
this.postList = postList;
}
// skipping other overridden methods
}
fetching data from Firebase Database in MainActivity using ChildEventListener as
mPostChildEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
try{
if (dataSnapshot.exists() && dataSnapshot.hasChildren() && !dataSnapshot.equals(null)){
Post post = dataSnapshot.getValue(Post.class);
mPostList.add(post);
mAllPostAdapter.notifyDataSetChanged();
} else {
///
}
} catch (Exception e){
e.printStackTrace();
}
}
// skipping other overridden methods
};
mPostQuery = getQuery(); // this method only retuns ==> return mPostDatabaseReference.limitToFirst(100);
mPostQuery.addChildEventListener(mPostChildEventListener);
now setting the RecyclerView as
mAllPostAdapter = new AllPostAdapter(MainActivity.this, mPostList);
mPostRecyclerView.setAdapter(mAllPostAdapter);
works perfect as it should, but I want to use the method setEnabled(false); On a view of RecyclerView item in adapter when it is clicked but I can not, asyou can see
I want that the like button be setEnabled(true); when the heatrs are inserted in DB because in onBindViewHolder() method of adapter where I'm setting click listener to like button I've to check if the post at the given position is liked by current user then the like button should be setEnabled(false); else behave as normal you can see here
#Override
public void onBindViewHolder(final PostHolder viewHolder, final int position) {
try{
final Post model = postList.get(position);
viewHolder.likeTextView.setText(String.valueOf(model.getHearts().size()));
// Here I'm if the post at the given position is liked by current user then the _like_ button should be setEnabled(false);
if (!model.hearts.containsKey(currentUserId)){
viewHolder.likeImageView.setOnLikeListener(new OnLikeListener() {
#Override
public void liked(LikeButton likeButton) {
Map<String, Boolean> likeWithUserId = new HashMap<>();
likeWithUserId.put(currentUserId, true);
mPostDatabaseReference.child(model.getPostId())
.child("hearts").setValue(likeWithUserId);
viewHolder.likeTextView.setText(String.valueOf(model.getHearts().size() + 1));
}
#Override
public void unLiked(LikeButton likeButton) {
}
});
} else {
viewHolder.likeImageView.setLiked(true);
viewHolder.likeImageView.setEnabled(false);
}
} catch (Exception e){
e.printStackTrace();
}
}
but you can see the line
if(model.hearts.containsKey(currentUserId))
is not fetching and resulting in real time that is the issue I want to resolve here further questions are welcome and I'll try to answer if any?
Simple thing I would like to do (see in the picture)
Display a view with info coming from 2 different places in Firebase so that it behaves in a professional way scrolling UP and DOWN
I have a list of movies and on each of them I would like the user to specify a rating and see it
In DB I created 2 structures to have the list of movies on one side and the ratings per user on the other
Problem using FirebaseRecyclerAdapter
My problem is that scrolling fast up and down the list, the visualization of the information coming from the second reference (the rating) is loaded on a different time (asynchronous call) and this is not acceptable to see this (little) delay building the view. Is this a limitation of FirebaseRecyclerView?
Because viewHolders are reused in the recycleView I reset and reload each time in populateView() the rating values and this doesn't help. Once retrieved I'm oblidged to get them again if the user scroll the view (see the setOnlistener in populateView()
Setting a listener in populateView cause also to have as many listener as the number of times populateView() is executed (if you scroll UP and DOWN it's many times).
Solutions / Workaround ?
Is there a correct way to do it preventing the problem? Or is it a limitation?
What about performance with my implementation where the listener is inside populateView() and there are MANY listener created?
Below some things I'm thinking on:
Prevent viewHolders to be recycled and just load once?
Override some other methods of RecyclerView? I tried with parseSnapshot() but it's the same problem...
Change the DB structure to have all the info in one list (I don't think it's the good one because it means adding rating information of each user to movie list)
Add a loading spinner on the rating part so that the rating is displayed only when the asyncrhonous call to firebase is completed (don't like it) without the today effect of: "changing star color in front of the user".
My Implementation
From FirebaseRecyclerAdapter
#Override
protected void populateViewHolder(final MovieViewHolder viewHolder, final Movie movie, final int position) {
String movieId = this.getRef(position).getKey();
// Oblidged to show no rating at the beginning because otherwise
// if a viewHolder is reused it has the values from another movie
viewHolder.showNoRating();
//---------------------------------------------
// Set values in the viewHolder from the model
//---------------------------------------------
viewHolder.movieTitle.setText(movie.getTitle());
viewHolder.movieDescription.setText(movie.getDescription());
//-----------------------------------------------------
// Ratings info are in another DB location... get them
// but call is asynchronous so PROBLEM when SCROLLING!
//-----------------------------------------------------
DatabaseReference ratingMovieRef = mDbRef.child(Constants.FIREBASE_LOCATION_RATINGS).child(currentUserId).child(movieId);
ratingQuoteRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
RatingMovie ratingMovie = dataSnapshot.getValue(RatingMovie.class);
Rating rating = Rating.NO_RATING;
if (ratingMovie != null) {
rating = Rating.valueOf(ratingMovie.getRating());
}
// Set the rating in the viewholder (through anhelper method)
viewHolder.showActiveRating(rating);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
from MovieViewHolder
public class QuoteViewHolder extends RecyclerView.ViewHolder {
public CardView cardView;
public TextView movieTitle;
public TextView movieDescription;
public ImageView ratingOneStar;
public ImageView ratingTwoStar;
public ImageView ratingThreeStar;
public QuoteViewHolder(View itemView) {
super(itemView);
movieTitle = (TextView)itemView.findViewById(R.id.movie_title);
movieDescription = (TextView)itemView.findViewById(R.id.movie_descr);
// rating
ratingOneStar = (ImageView)itemView.findViewById(R.id.rating_one);
ratingTwoStar = (ImageView)itemView.findViewById(R.id.rating_two);
ratingThreeStar = (ImageView)itemView.findViewById(R.id.rating_three);
}
/**
* Helper to show the color on stars depending on rating value
*/
public void showActiveRating(Rating rating){
if (rating.equals(Rating.ONE)) {
// just set the good color on ratingOneStar and the others
...
}
else if (rating.equals(Rating.TWO)) {
// just set the good color
...
} else if (rating.equals(Rating.THREE)) {
// just set the good color
...
}
/**
* Initialize the rating icons to unselected.
* Important because the view holder can be reused and if not initalised values from other moviecan be seen
*/
public void initialiseNoRating(){
ratingOneStar.setColorFilter(ContextCompat.getColor(itemView.getContext(), R.color.light_grey));
ratingTwoStar.setColorFilter(....
ratingThreeStar.SetColorFilter(...
}
You can sort of cache the ratings using a ChildEventListener. Basically just create a separat one just for the Ratings node, and have it store the ratings in a Map. Then using the RecyclerAdapter you will retrieve from the Map if the rating is available, if it is not, have the rating listener update the recyclerview as soon as is has downloaded the rating. This is one strategy you could go about, doing it, you will have to manually copy/paste some classes from the FirebaseUI library and set some fields public for this to work.
Usage would be something like this
private MovieRatingConnection ratingConnection;
// inside onCreate
ratingConnection = new MovieRatingConnection(userId, new MovieRatingConnection.RatingChangeListener() {
#Override
public void onRatingChanged(DataSnapshot dataSnapshot) {
if (recyclerAdapter != null) {
if (dataSnapshot != null) {
int index = recyclerAdapter.snapshots.getIndexForKey(dataSnapshot.getKey());
recyclerAdapter.notifyItemChanged(index);
}
}
}
});
Query movieQuery = FirebaseDatabase.getInstance().getReference().child("Movies");
recyclerAdapter = new FirebaseRecyclerAdapter(movieQuery...) {
#Override
public void populateViewHolder(RecyclerView.ViewHolder viewHolder, Object model, int position) {
//...
final String key = getRef(position).getKey();
viewHolder.showActiveRating(ratingConnection.getRating(key));
}
};
and MovieRatingConnection would be a class like this
public class MovieRatingConnection {
private MovieRatingListener listener;
public MovieRatingConnection(String userId, RatingChangeListener changeListener) {
Query query = FirebaseDatabase.getInstance().getReference().child("MovieRatings").child(userId);
listener = new MovieRatingListener(query, changeListener);
}
public Rating getRating(String key) {
return listener.getRating(key);
}
public void cleanup() {
if (listener != null) {
listener.unregister();
}
}
public static class MovieRatingListener implements ChildEventListener {
public interface RatingChangeListener {
public void onRatingChanged(DataSnapshot snapshot);
}
private Query query;
private HashMap<String, Rating> ratingMap = new HashMap<>();
private RatingChangeListener changeListener;
public MovieRatingListener(Query query, RatingChangeListener changeListener) {
this.query = query;
this.changeListener = changeListener;
query.addChildEventListener(this);
}
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot != null) {
ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
changeListener.onRatingChanged(dataSnapshot);
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
if (dataSnapshot != null) {
ratingMap.put(dataSnapshot.getKey(), dataSnapshot.getValue(Rating.class));
changeListener.onRatingChanged(dataSnapshot);
}
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
ratingMap.remove(dataSnapshot.getKey());
changeListener.onRatingChanged(null);
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
public Rating getRating(String key) {
if (ratingMap.get(key) != null) {
return ratingMap.get(key);
} else {
return new Rating(); // default value/null object
}
}
public void unregister() {
query.removeEventListener(this);
}
}
}
I'm simply just trying to populate data from Firebase Database into my listview. The logs are showing the data is being retrieved, but the adapter won't set the values to the text in a single list item in the list? All it says is "No setter/field for INSERT VALUE". Which makes me think that I didn't have my setters made correctly but there were auto generated by Android Studio. I don't know what I am missing here. Any help is appreciated.
NODE OBJECT
package com.megliosolutions.ipd.Objects;
import android.graphics.Bitmap;
/**
* Created by Meglio on 6/13/16.
*/
public class NodeObject {
public String mStaticAddress;
public String mLat;
public String mLong;
public NodeObject(){
//needed for firebase
}
public NodeObject(String address, String lat, String Long){
this.mStaticAddress = address;
this.mLat = lat;
this.mLong = Long;
}
public String getmStaticAddress() {
return mStaticAddress;
}
public void setmStaticAddress(String mStaticAddress) {
this.mStaticAddress = mStaticAddress;
}
public String getmLat() {
return mLat;
}
public void setmLat(String mLat) {
this.mLat = mLat;
}
public String getmLong() {
return mLong;
}
public void setmLong(String mLong) {
this.mLong = mLong;
}
}
STATIC LISTADAPTER
/**
* Created by Meglio on 6/14/16.
*/
public class StaticListAdapter extends ArrayAdapter<NodeObject> {
public static String TAG = StaticListAdapter.class.getSimpleName();
public Context mContext;
public List<NodeObject> mNodes;
public class ViewHolder {
TextView mStaticAddress;
TextView mLAT;
TextView mLONG;
}
#Override
public int getCount() {
return mNodes.size();
}
public StaticListAdapter(Context context, List<NodeObject> objects) {
super(context, R.layout.activity_main, objects);
this.mContext = context;
this.mNodes = objects;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = new ViewHolder();
NodeObject node = mNodes.get(position);
if (convertView == null) {
convertView = LayoutInflater.from(this.mContext).inflate(R.layout.node_item, null);
holder.mLONG = (TextView) convertView.findViewById(R.id.node_item_LONG);
holder.mStaticAddress = (TextView) convertView.findViewById(R.id.node_item_IP);
holder.mLAT = (TextView) convertView.findViewById(R.id.node_item_LAT);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mStaticAddress.setText(node.getStaticAddress());
holder.mLONG.setText(node.getLongitude());
holder.mLAT.setText(node.getLatitude());
return convertView;
}
}
MAINACTIVITY
public class MainActivity extends AppCompatActivity {
public static String TAG = MainActivity.class.getSimpleName();
public ListView main_ListView;
public FirebaseAuth mAuth;
public FirebaseUser mUser;
public DatabaseReference mDatabase;
//Strings
public String static_ip;
public String lat = "5.0";
public String mLong = "4.0";
public String currentUser;
//Adapters
public StaticListAdapter listAdapter;
//Node Object
NodeObject node;
public List<NodeObject> nodesList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Instances
mAuth = FirebaseAuth.getInstance();
mUser = mAuth.getCurrentUser();
mDatabase = FirebaseDatabase.getInstance().getReference();
currentUser = mUser.getUid();
main_ListView = (ListView)findViewById(R.id.Main_listview);
//Toolbar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//[End of Toolbar]
nodesList = new ArrayList<>();
retrieveData();
listAdapter = new StaticListAdapter(getApplicationContext(),nodesList);
main_ListView.setAdapter(listAdapter);
Log.i(TAG, "USER: " + currentUser);
}
private void retrieveData() {
mDatabase.child("nodes").child(mUser.getUid())
.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
NodeObject nodeObject = dataSnapshot.getValue(NodeObject.class);
listAdapter.add(nodeObject);
listAdapter.setNotifyOnChange(true);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.getGPS:
//nothing
return true;
case R.id.addNode:
addNode();
return true;
case R.id.logout:
signOut();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void signOut() {
mAuth.signOut();
Intent intent = new Intent(MainActivity.this, Login.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Toast.makeText(getApplicationContext(), "Logging Out.", Toast.LENGTH_SHORT).show();
startActivity(intent);
}
private void addNode() {
//AlertDialog
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.setTitle("Dude, assign something...");
LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.main_add_node_dialog, null);
dialogBuilder.setView(dialogView);
final EditText editText = (EditText)
dialogView.findViewById(R.id.static_et);
dialogBuilder.setPositiveButton("Assign", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
static_ip = editText.getText().toString();
String ip = static_ip;
node = new NodeObject(ip, lat, mLong);
mDatabase.child("nodes").child(currentUser).push().setValue(node);
Toast.makeText(getApplicationContext(), "Static IP: " + static_ip + " assigned!"
, Toast.LENGTH_SHORT).show();
}
}).
setNegativeButton("Or Not...", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "Fine, nvm then..."
, Toast.LENGTH_SHORT).show();
}
});
dialogBuilder.create().show();
}
}
Debugging shows that I am able to get everything client side. Logs show this as well.
06-18 18:25:42.981 12962-12962/com.megliosolutions.ipd D/MainActivity: zHF4TGnRvkeXEbKiLegiUNLGHX12:{-KKLeBAe9pV1Umm3qQMo={mStaticAddress=26161910494949, mLong=3.0, mLat=2.0}, -KKG_ACFvdX7aJOR98-o={mStaticAddress=10.223.22.250, mLong=3.0, mLat=2.0}, -KKWKMZS7WkE_xWbL3rC={mStaticAddress=, mLong=4, mLat=5}, -KKQQLITf9-7iMFlqEWR={mStaticAddress=123123123123, mLong=3.0, mLat=2.0}, -KKG_J6PKwogjBFdk52Z={mStaticAddress=10.333.555.888, mLong=3.0, mLat=2.0}}
UPDATE
The part that didn't make sense to me, but I'm sure makes sense to those who know it very well. Is comprehending what I'm reading the firebase documentation. I read over it a few time, but I guess it just wasn't clicking. I figured out that structuring the data in firebase is KEY. Without that you can't code properly because everything relies on that. Firebase makes it easy to use now that I see it working. I will be making a blog post on this to explain my troubles and how to surpass them so no one else runs into this mess. The below code will build a functional listview with firebase backend!
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());
NodeObject nodeObject = dataSnapshot.getValue(NodeObject.class);
listAdapter.add(nodeObject);
listAdapter.setNotifyOnChange(true);
}
In retrieveData(), you should be using a ChildEventListener, not a ValueEventListener. You are adding Node objects as children of node/$uid with the keys generated by push(). The ValueEventListener is returning a Map<String,Object> containing all of the Nodes. That is shown in the logcat output you posted. You can use the onChildAdded() callback of ChildEventListener to get each Node as it is created and then add it to your adapter.
The warning is because the casing mismatches between your field and you setter.
Following the examples in the Firebase documentation, this seems like the better way to model the Java class:
public class NodeObject {
public String staticAddress;
public String lat;
public String lon;
public NodeObject(){
//needed for firebase
}
public NodeObject(String address, String lat, String lon){
this.staticAddress = address;
this.lat = lat;
this.lon = lon;
}
}
As an added bonus this will lead to more sensible JSON property names too.
I had the same issue just now & the answer was pretty frustrating.
I think that the issue is with your naming convention. For example, it looks like you named a variable mLat, and then your accessors/mutators are getmLat() and setmLat(). I think when Firebase is doing the deserialization, they rely on certain naming. For example, if you have a member variable named lat, you will need to have getLat() and setLat().
For your case, you might just be able to change your methods to be getMLat() and setMLat(). Although I'd suggest changing up your conventions a bit in general.
As a side note, know that your naming is typically against convention. Generally, variables prefixed by m are private member variables, accessed & mutated by public methods (like yours).
I was getting an error because I was accessing wrong variable(parent variable)
public void onDataChange(#NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot : snapshot.getChildren()){
list.add(snapshot.getValue(CategoryModel.class));
// Here in list I should have added dataSnapshot.getValue(CategoryModel.class)
}
}
It may help.