Cannot Access Recyclerview Position Using FirbaseUI with Cloud Firestore - java

I am migrating my application from the Firebase Database to the Firebase Cloud Firestore.
Previously, I was using the FirebaseUI for the realtime database. After initializing all of my options and creating the adapter in the realtime database, I called .setOnClickListener() on a View of the RecyclerView to navigate to a new activity:
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent toClickedPoll = new Intent(getActivity(), PollHostActivity.class);
toClickedPoll.putExtra("POLL_ID", mFireAdapter.getRef(holder.getAdapterPosition()).getKey());
startActivity(toClickedPoll);
}
});
The extra I passed was the key of the location in my Firebase realtime database.
I am trying to access the same "key" via the FirebaseUI for the Cloud Firestore. Below is my code:
mFirestoreAdaper = new FirestoreRecyclerAdapter<Poll, PollHolder>(storeOptions) {
#Override
protected void onBindViewHolder(#NonNull final PollHolder holder, int position, #NonNull Poll model) {
holder.mPollQuestion.setText(model.getQuestion());
String voteCount = String.valueOf(model.getVote_count());
//TODO: Investigate formatting of vote count for thousands
holder.mVoteCount.setText(voteCount);
Picasso.with(getActivity().getApplicationContext())
.load(model.getImage_URL())
.fit()
.into(holder.mPollImage);
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent toClickedPoll = new Intent(getActivity(), PollHostActivity.class);
String position = String.valueOf(mFirestoreAdaper.getItemId(holder.getAdapterPosition()));
Log.v("Firestore ID", position);
toClickedPoll.putExtra("POLL_ID", position);
startActivity(toClickedPoll);
}
});
}
Right now, the position variable (I have logged) is returning -1. I essentially want it to return the Poll document below:

Try this:
String positions = getSnapshots().getSnapshot(position).getId();
Log.v("Firestore ID", positions);
toClickedPoll.putExtra("POLL_ID", positions);
instead of this:
String position = String.valueOf(mFirestoreAdaper.getItemId(holder.getAdapterPosition()));
Log.v("Firestore ID", position);
toClickedPoll.putExtra("POLL_ID", position);

Related

(Android App) Is there a way to retrieve data from a Firebase database while inside the SnapshotParser part of the FirebaseRecyclerOptions query?

So, as the title says, I am looking to retrieve data from a Firebase database before constructing an object in FirebaseRecyclerOptions to be used in a FirebaseRecyclerAdapter. Basically, what I am trying to do is make a friends list in an app I'm working on. This what the database looks like:
Friends:
uid1:
id: friendID
uid2:
id: friendID
Users:
uid1:
name: name
status: status
image: profileImageUrl
uid2:
name: name
status: status
image: profileImageUrl
I've got code that currently looks like this:
FirebaseRecyclerOptions<Users> options = new FirebaseRecyclerOptions.Builder<Users>().setQuery(usersDatabase, new SnapshotParser<Users>() {
#NonNull
#Override
public Users parseSnapshot(#NonNull DataSnapshot snapshot) {
System.out.println(snapshot);
rootRef.child("Users").child(snapshot.getValue().toString()).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
System.out.println(dataSnapshot);
name = dataSnapshot.child("name").getValue().toString();
status = dataSnapshot.child("status").getValue().toString();
image = dataSnapshot.child("image").getValue().toString();
return;
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
System.out.println(snapshot);
return new Users(name, image, status);
}
}).build();
The problem is that the ValueEventListener I add does not trigger until after the new Users instance is returned. Should I be adding the ValueEventListener to the same DatabaseReference (userDatabase) as the FirebaseRecyclerOptions query?
What you're trying to do isn't really possible with FirebaseUI. The snapshot parser needs to return a User object immediately, or synchronously. You can't perform an asynchronous database query (which does not complete immediately, or even guaranteed to complete at all) in order to provide that value.
If you need to perform multiple queries in order to populate your views, you won't be able to use FirebaseUI effectively. You should probably consider doing all your lookups ahead of time, or write a special adapter that allows view contents to be populated asynchronously as the results become available. This will end up being a lot of code to do correctly.
It perhaps seems a little redundant to be answering my own question, but this is mostly for anyone else that has trouble with this. Following #Doug Stevenson's suggestion, I started trying to make my own custom recycler adapters and options class. However, I realized that the queries for the options could be modified. So basically, the solution is this:
Query query = database.collection("Users");
#Override
protected void onStart() {
super.onStart();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DocumentReference ref = FirebaseFirestore.getInstance().collection("Users").document(uid);
ref.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot document = task.getResult();
if (document.exists()) {
friends = (ArrayList<String>) document.get("friends");
if (friends.size() > 0) {
FirestoreRecyclerOptions<Users> options = new FirestoreRecyclerOptions.Builder<Users>().setQuery(query.whereIn("user_id", friends), Users.class).build();
FirestoreRecyclerAdapter<Users, UsersViewHolder> adapter = new FirestoreRecyclerAdapter<Users, UsersViewHolder>(options) {
#Override
public UsersViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_user_layout, parent, false);
return new UsersViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull UsersViewHolder usersViewHolder, int i, #NonNull Users users) {
if (users != null) {
usersViewHolder.setName(users.name);
usersViewHolder.setStatus(users.status);
usersViewHolder.setImg(users.image);
final String userID = getSnapshots().getSnapshot(i).getId();
usersViewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent profilePage = new Intent(FriendsList.this, ProfileActivity.class);
profilePage.putExtra("userID", userID);
startActivity(profilePage);
}
});
}
}
};
usersListPage.setAdapter(adapter);
adapter.startListening();
}
}
}
}
});
}
In the setQuery method, rather than using the collection reference as the query, I created a query object from it, and then modified the query query.whereIn(), which allows you to check to see if the field of a document contains the given object or one of the objects in a list.
My code here is very much a mess, I know.

How to edit a child value when under a firebase auto generated key

I am trying to edit the value of "shopB" in my database. I am doing so inside of a view holder, by use of a dialog box with an edit text field and an approve button. When a user clicks on a specific transaction, they're given a dialog box where they enter the value for shopB and then click Approve.
I am struggling to do this as I cannot access that value because of the uniquely generated key that firebase has. I have many posts with similar problems to mine but as I am doing this inside of a view holder I do not see how I can use DataSnapshot. Any help would be greatly appreciated as I am getting very lost.
Database Structure:
viewHolder.setItemClickListener(new ItemClickListener() {
#Override
public void onClick(android.view.View view, int position, boolean isLongClick) {
Toast.makeText(Request.this, "Receiving: " + shopA, Toast.LENGTH_SHORT).show();
ThisDialog = new Dialog(Request.this);
ThisDialog.setContentView(R.layout.dialog_template);
final EditText Write = (EditText) ThisDialog.findViewById((R.id.write));
Button Approve = (Button) ThisDialog.findViewById(R.id.approve);
Write.setEnabled(true);
Approve.setEnabled(true);
Approve.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String newShopB = Write.getText().toString().replace(".", " ");
transaction.child("key").child("shopB").setValue(newShopB);
Toast.makeText(Request.this, "CustB Approval", Toast.LENGTH_SHORT).show();
ThisDialog.cancel();
}
});
ThisDialog.show();
}
});
This would be my code but obviously where I have "transaction.child("key")" does not find the key.
Oh and transaction is defined earlier in my code as
database = FirebaseDatabase.getInstance();
start = database.getReference("Transaction");
transaction = start.child(passedEmail);
Here's a possible solution to workaround on your project:
start.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.child(key).getChildren()) {
//insert additional codes here
}
}
});

Android Recylcerview set Position of Item programmatically

i created a recylcerview for myChats. In this Recyclerview i can see a thumb, the last message and the name. if i send or receive a new message the item should go to first position, like in whatsapp. to receive the new message i created the following method:
private void getLastMSG(final String userId, final TextView lastMSG){
String userid = FirebaseAuth.getInstance().getCurrentUser().getUid();
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Users").child(userid).child("connections").child("matches").child(userId);
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.exists()){
String lastMsg = dataSnapshot.child("lastMsg").getValue().toString();
lastMSG.setText(lastMsg);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Now i want to set this item to first position but i dont know how
You need to call:
notifyItemMoved(oldPos, newPos) in your recyclerview adapter.
Note that you need to update your data model before that, in order to bind correct view in onBindViewHolder method.
Referece to adapter documentation.

Need Help Refreshing my database ListView in my app

My app layout apparently isn't a normal layout so I a having trouble setting my List Adapter to auto updated when an edit is made.
I make my edits to my database in this Java File which is controlled in its own activity and layout.
public void onClick(View view){
if (view == findViewById(R.id.addsave)) {
RecipeRepo repo = new RecipeRepo(this);
Recipe recipe = new Recipe();
if (editTextName.getText().toString().equals("")) {
editTextName.setError("Recipe name required!");
return;
} else {
recipe.name = editTextName.getText().toString();
}
if (textImagePath.getText().toString().equals("") ) {
recipe.image = ("");
}else{
recipe.image = textImagePath.getText().toString();
}
recipe.category = staticSpinner.getSelectedItem().toString();
if (editTextIngredients.getText().toString().equals("")) {
editTextIngredients.setError("Ingredient required!");
return;
} else {
recipe.ingredients = editTextIngredients.getText().toString();
}
if (editTextInstruct.getText().toString().equals("")) {
editTextIngredients.setError("Instruction required!");
return;
} else {
recipe.instructions = editTextInstruct.getText().toString();
}
recipe.cooktemp = editTextCookTemp.getText().toString();
recipe.cooktime = editTextCookTime.getText().toString();
recipe.serves = editTextServings.getText().toString();
recipe.recipe_Id = _Recipe_Id;
if (_Recipe_Id == 0) {
_Recipe_Id = repo.insert(recipe);
Toast.makeText(this, "New Recipe Added", Toast.LENGTH_SHORT).show();
finish();
it actually inserts and updates in this java file
int insert(Recipe recipe){
//Open connection to write data
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(Recipe.KEY_SERVES, recipe.serves);
values.put(Recipe.KEY_COOKTIME, recipe.cooktime);
values.put(Recipe.KEY_COOKTEMP, recipe.cooktemp);
values.put(Recipe.KEY_INSTRUCT, recipe.instructions);
values.put(Recipe.KEY_INGREDIENTS, recipe.ingredients);
values.put(Recipe.KEY_CATEGORY, recipe.category);
values.put(Recipe.KEY_IMAGE, recipe.image);
values.put(Recipe.KEY_NAME, recipe.name);
//Inserting Row
long recipe_Id = db.insert(Recipe.TABLE, null, values);
db.close();// Closing database connection
return (int) recipe_Id;
}
void delete(int recipe_Id){
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(Recipe.TABLE, Recipe.KEY_ID + "=?", new String[] {String.valueOf(recipe_Id)});
db.close();
}
void update(Recipe recipe){
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(Recipe.KEY_SERVES, recipe.serves);
values.put(Recipe.KEY_COOKTIME, recipe.cooktime);
values.put(Recipe.KEY_COOKTEMP, recipe.cooktemp);
values.put(Recipe.KEY_INSTRUCT, recipe.instructions);
values.put(Recipe.KEY_INGREDIENTS, recipe.ingredients);
values.put(Recipe.KEY_CATEGORY, recipe.category);
values.put(Recipe.KEY_IMAGE, recipe.image);
values.put(Recipe.KEY_NAME, recipe.name);
db.update(Recipe.TABLE, values, Recipe.KEY_ID + "=?", new String[]{String.valueOf(recipe.recipe_Id)});
db.close();
}
and lastly it gets put into the list view from this Java file and separate layout. Which is where my adapters are but i cannot get the notifyDataSetChanged() to work here at all... as in it wont even come up.
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
RecipeRepo repo = new RecipeRepo(this);
if (id == R.id.nav_meat) {
final ArrayList<HashMap<String, String>> recipeList = repo.getRecipeMeat();
if(recipeList.size()!=0) {
ListView lv = (ListView) findViewById(R.id.list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
recipe_Id = (TextView) view.findViewById(R.id.recipe_Id);
String recipeId = recipe_Id.getText().toString();
Intent objIndent = new Intent(getApplicationContext(), RecipeDetail.class);
objIndent.putExtra("recipe_Id", Integer.parseInt(recipeId));
startActivity(objIndent);
}
});
ListAdapter adapter = new SimpleAdapter(SousChef.this, recipeList, R.layout.view_recipe_entry, new String[]{"id", "category", "name"}, new int[]{R.id.recipe_Id, R.id.recipe_list_category, R.id.recipe_list_name});
lv.setAdapter(adapter);
}else {
Toast.makeText(this, "No recipe!", Toast.LENGTH_SHORT).show();
}
} else if (id == R.id.nav_veg) {
final ArrayList<HashMap<String, String>> recipeList = repo.getRecipeVeg();
if(recipeList.size()!=0) {
ListView lv = (ListView) findViewById(R.id.list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
recipe_Id = (TextView) view.findViewById(R.id.recipe_Id);
String recipeId = recipe_Id.getText().toString();
Intent objIndent = new Intent(getApplicationContext(), RecipeDetail.class);
objIndent.putExtra("recipe_Id", Integer.parseInt(recipeId));
startActivity(objIndent);
}
});
ListAdapter adapter = new SimpleAdapter(SousChef.this, recipeList, R.layout.view_recipe_entry, new String[]{"id", "category", "name"}, new int[]{R.id.recipe_Id, R.id.recipe_list_category, R.id.recipe_list_name});
lv.setAdapter(adapter);
}else {
Toast.makeText(this, "No recipe!", Toast.LENGTH_SHORT).show();
}
So any advise on setting this up to automatically update would be a huge help. I have been racking my brain over this for a couple days now looking at different examples and what not, but no setup is quite like this one which doesnt allow me to have everything in one file.
And thank you in advance.
category picking image:
Category picking Image
There are for sure more answers but this is one that might help,
Quick Example for the proposed solution
SHORT EXPLANATION
inside MainActivity
//create a public static adapter
public static ListAdapter adapter
inside onCreateView()
//Create your adapter and set it to the right ListView
ListView lv = findViewById(R.id.listView_in_xml);
adapter = new SimpleAdapter(...)
lv.setAdapter(adapter)
inside CustomAdapter which in your case I assume is SimpleAdapter
//add a public method to be called so that the Adapter updates and displays the new data
public void updateMethod(){
//update your List<Recipe> that I would guess you have calling the database again
//if needed update your getCount() return value so that it returns the number of childs in your ListView which most of the cases is just the List<Recipe>.size()
//notifyDataSetChanged()
}
inside your DB HANDLER CLASS
//in every update, add, delete or any method that requires the ListView to Update just call the created method,
MainActivity.CustomAdapter.updateMethod();
PROBLEMS
You will have to make sure the public static adapter has been initialized and is not null, or simply check whether the adapter is not null and update, because if the adapter is null that activity has not launched yet thus no need to trigger the updateMethod().
OTHER SOLUTIONS
Instead of creating a public static adapter create a public static boolean, then whenever data changes set that boolean to true from the database.
Finally, whenever you resume your activity check against that boolean and update your ListViewAdapter if needed.
MORE COMPLICATED SOLUTIONS WHICH I KNOW WORK CAUSE I USE IT
Use TaskAsyncTaskLoader which utilizes a Loader in your MainActivity and implements LoaderManager.LoaderCallbacks.
Optionally, you can make the Loader be, public static Loaderand inside your DBHandler you trigger the loader to load the data again or use any other logic you want.
Proofs of Working suggested solution,
You can Broadcast Intent from the change database file after you get the response in the onCreate() of adapter loading class
Intent intent = new Intent("key_to_identify_the_broadcast");
Bundle bundle = new Bundle();
bundle.putString("edttext", "changed");
intent.putExtra("bundle_key_for_intent", bundle);
context.sendBroadcast(intent);
and then you can receive the bundle in your fragment by using the BroadcastReceiver class
private final BroadcastReceiver mHandleMessageReceiver = new
BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle =
intent.getExtras().getBundle("bundle_key_for_intent");
if(bundle!=null){
String edttext = bundle.getString("edttext");
}
//you can call any of your methods for using this bundle for your use case
}
};
in onCreate() of your adapter adding class you need to register the broadcast receiver first otherwise this broadcast receiver will not be triggered
IntentFilter filter = new IntentFilter("key_to_identify_the_broadcast");
getActivity().getApplicationContext().
registerReceiver(mHandleMessageReceiver, filter);
Finally you can unregister the receiver to avoid any exceptions
#Override
public void onDestroy() {
try {
getActivity().getApplicationContext().
unregisterReceiver(mHandleMessageReceiver);
} catch (Exception e) {
Log.e("UnRegister Error", "> " + e.getMessage());
}
super.onDestroy();
}

#Askfirebase Get the previous item values(POJO) in firebase recycler adapter in android

AskFirebase How to get the previous item values(POJO) in firebase recycler adapter without using database query.
// Set up FirebaseRecyclerAdapter with the Query
Query postsQuery = getQuery(mDatabase);
mAdapter = new FirebaseRecyclerAdapter<Post, PostViewHolder>(Post.class, R.layout.item_post,
PostViewHolder.class, postsQuery) {
#Override
protected void populateViewHolder(final PostViewHolder viewHolder, final Post model, final int position) {
final DatabaseReference postRef = getRef(position);
Log.e(TAG, "populateViewHolder: " + position);
// Set click listener for the whole post view
final String postKey = postRef.getKey();
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Launch PostDetailActivity
Intent intent = new Intent(getActivity(), PostDetailActivity.class);
intent.putExtra(PostDetailActivity.EXTRA_POST_KEY, postKey);
startActivity(intent);
}
});
// Determine if the current user has liked this post and set UI accordingly
if (model.stars.containsKey(getUid())) {
viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_24);
} else {
viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_outline_24);
}
// Bind Post to ViewHolder, setting OnClickListener for the star button
viewHolder.bindToPost(model, new View.OnClickListener() {
#Override
public void onClick(View starView) {
// Need to write to both places the post is stored
Log.e(TAG, "new: ");
DatabaseReference globalPostRef = mDatabase.child("posts").child(postRef.getKey());
DatabaseReference userPostRef = mDatabase.child("user-posts").child(model.uid).child(postRef.getKey());
// Run two transactions
onStarClicked(globalPostRef);
onStarClicked(userPostRef);
}
});
}
};
mRecycler.setAdapter(mAdapter);
Suppose their are five cell list whenever i am facing second cell in the list that time i want to put a condition based on first cell value. So how i can fatch the value of first cell?
I already try to using arraylist to store the POJO of Post . But the problem is whenever some item is deleted from firebase table that item onDataChange call but populateViewHolder doesn't call. Their is also a way to get previous data using database query that is
DatabaseReference ref = getRef(position-1);
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
Log.e(TAG, "CHild exist: ");
} else {
Log.e(TAG, "no CHild exist: ");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
But i don't want to use this database query is their any other way?
The Design Firebase data structure for topic Answer and Comment its like your problem.
gdtdg6765rf and hjgdhs567hd are unique key get by firebase
hjgdhs567hd is answer
gdtdg6765rf is comment to answer hjgdhs567hd
created is -1*UNIX Timestamp for ordering
date, time and toanswer was saved in comments by answer belong to
if to delete answer set all flags "deleted=1" where child "toanswer=deleted answer key" to populate again
#eurosecom above image is my layout where their is a recycler view which populate through FirebaseRecyclerAdapter . Now those green cell is my single cell. you see a red circle which denote the date. in case of position==0 I just simple visible the layout, and in case of position>0 i want to put the condition based on previous item date.
Now in my FirebaseRecyclerAdapter i have to put the condition so i have to fetch the previous position date. So as i am already doing a network oparetion using Query to fetch the msg list i don't want to put addListenerForSingleValueEvent in the populateview again as because it will again fetch the val from database. So is their any other way to get the previous item?

Categories