I am not sure if I am understanding some of the literature correctly. Android documentation says don't create unecessary objects. This article (Are Firebase queries scalable) mentions scalability with DB queries is ok, but I've also read its better to store your Query into an ArrayList and search through that instead of querying a large DB.
In my case I am using Firebase Realtime Database for Android and I'm wondering if I get a Child Node that has maybe 200 examples/child nodes, should I put each of these snapshots into a Model Class and then Add each of those to an ArrayList which can then be displayed in a RecyclerView? Or Should I run .getValue() on the fields and store them in another way?
I am specifically looking to see which Company ID's an Employer is associated to, and then go to the Companies node and Get the Business Names and Business Cities for that Employer
DB:
Here is my section of code in the activity:
companiesRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds: dataSnapshot.getChildren()) {
Log.i("companiesSnap", ds.toString());
Log.i("KEYs", ds.getKey());
companyIDKey = ds.getKey();
//For each company ID in the Arraylist
for (int i = 0; i < myCompanyIDsList.size(); i++) {
//IF the IDs in arraylist from employee matches CompanyID from Companies node
if(myCompanyIDsList.get(i).equals(companyIDKey)) {
//IF THE ID matches, then get the associated company info
Log.i("city", ds.child("businessCity").getValue().toString());
Log.i("name", ds.child("businessName").getValue().toString());
Businesses business = new Businesses(ds);
myBusinessListItems.add(business);
mAdapter.updateDataSet(myBusinessListItems);
}
}
Entire Class:
public class BusinessesActivity extends Activity {
private Context mContext;
LinearLayout mLinearLayout;
private RecyclerView mRecyclerView;
private MyAdapterBusiness mAdapter = new MyAdapterBusiness(this);
private RecyclerView.LayoutManager mLayoutManager;
ArrayList<Businesses> myBusinessListItems;
ArrayList<String> myCompanyIDsList;
DatabaseReference employeesRefID, companiesRef;
FirebaseDatabase database;
String currentUser, companyIDKey;
TextView getData;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_businesses);
//RECYCLERVIEW STUFF
mRecyclerView = (RecyclerView) findViewById(R.id.b_recycler_view);
mContext = getApplicationContext(); // Get the application context
// Define a layout for RecyclerView
mLayoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false); //mLayoutManager = new LinearLayoutManager(mContext);
mRecyclerView.setLayoutManager(mLayoutManager);
// Initialize a new instance of RecyclerView Adapter instance
mRecyclerView.setAdapter(mAdapter);
//ARRAY List to Store EACH Company ID
myCompanyIDsList = new ArrayList<String>();
myBusinessListItems = new ArrayList<Businesses>();
currentUser =
FirebaseAuth.getInstance().getCurrentUser().getUid();
database = FirebaseDatabase.getInstance();
getData = (TextView) findViewById(R.id.getData);
companiesRef = database.getReference("Companies").child("CompanyIDs");
final DataSnapshotCallback callback = new DataSnapshotCallback() {
#Override
public void gotDataSnapshot(DataSnapshot snapshot) {
EmployeeUser employee = new EmployeeUser(snapshot);
//myCompanyIDsList.add(employee);
try {
for (DataSnapshot ds : snapshot.getChildren()) {
//WITHIN each UserId check the PushID
Log.i("TAG", "checkIfIDExists: datasnapshot: " + ds);
myCompanyIDsList.add(ds.getValue(EmployeeUser.class).getID());
Log.i("arrayList", myCompanyIDsList.toString());
//}
}
//GO THROUGH EACH COMPANY ID AND FIND INFORMATION
companiesRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds: dataSnapshot.getChildren()) {
Log.i("companiesSnap", ds.toString());
Log.i("KEYs", ds.getKey());
companyIDKey = ds.getKey();
//For each company ID in the Arraylist
for (int i = 0; i < myCompanyIDsList.size(); i++) {
//IF the IDs in arraylist from employee matches CompanyID from Companies node
if(myCompanyIDsList.get(i).equals(companyIDKey)) {
//IF THE ID matches, then get the associated company info
Log.i("city", ds.child("businessCity").getValue().toString());
Log.i("name", ds.child("businessName").getValue().toString());
Businesses business = new Businesses(ds);
myBusinessListItems.add(business);
mAdapter.updateDataSet(myBusinessListItems);
}
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("Cancelled", databaseError.toString());
}
}); //END COMPANIES EVENT LISTENER
//} //END FOR
} //END TRY
catch (Exception e) {
Log.i("FNull?", e.toString());
}
//mAdapter.updateDataSet(myListItems);
}
};
getData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View view) {
employeesRefID = database.getReference("Employees").child(currentUser).child("Companies"); //SEE HOW ADD EMPLOYEES checks
employeesRefID.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
callback.gotDataSnapshot(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("Cancelled", databaseError.toString());
}
}); //END EMPLOYEE EVENT LISTENER
Log.i("OutsideEvent", myCompanyIDsList.toString());
}
}); //END ONCLICK
Log.i("OutsideonClick", myCompanyIDsList.toString());
}
interface DataSnapshotCallback {
void gotDataSnapshot(DataSnapshot snapshot);
}
}
The trick to reducing memory usage when using Firebase is to only load data that you're going to display to the user.
If you need a list of just business names, create a nodes with just the business names in the database and display that. That way you've reduce both the bandwidth and the memory used, since you're not loading the other properties of the company.
You'll typically have one "master list" with all properties of each company (or other entity type). And then you may have one or more "display lists" that contain only the information of the business that you need to display in certain areas. This duplicated data is quite common in NoSQL databases, and is known as denormalization.
For a better introduction, read Denormalizing Your Data is Normal, NoSQL data modeling, and watch Firebase for SQL developers.
Related
How to get the data under foods (product Name)?
I can get the data (with the check icon).
I want to get the data from the wrong icon.
Here is my MainActivity code for database reference
// getting Firebase Database reference to communicate with firebase database
private final DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
// creating List of MyItems to store user details
private final List<MyItems> myItemsList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// getting RecyclerView from xml file
final RecyclerView recyclerView = findViewById(R.id.recyclerView);
// setting recyclerview size fixed for every item in the recyclerview
recyclerView.setHasFixedSize(true);
// setting layout manager to the recyclerview. Ex. LinearLayoutManager (vertical mode)
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
// clear old items / users from list to add new data/ users
myItemsList.clear();
// getting all children from users root
for (DataSnapshot Requests : snapshot.child("Requests").getChildren()) {
// to prevent app crash check if the user has all the details in Firebase Database
if (Requests.hasChild("askfor") && Requests.hasChild("tablet") && Requests.hasChild("total")) {
// getting users details from Firebase Database and store into the List one by one
final String getaskfor = Requests.child("askfor").getValue(String.class);
final String gettablet =Requests.child("tablet").getValue(String.class);
final String gettotal =Requests.child("total").getValue(String.class);
// final String getproductName =Requests.child("productName").getValue(String.class);
// creating user item with user details
MyItems myItems = new MyItems(getaskfor, gettablet, gettotal);
The foods node in your database looks like an array, so if you get its value in Java code you'll get a List.
So you can loop over the food children just as you do already for the requests children:
if (Requests.hasChild("foods") {
for (DataSnapshot foodSnapshot : Requests.child("foods").getChildren()) {
String productName = foodSnapshot.child("productName").getValue(String.class);
...
}
}
Unrelated: since your onDataChange implementation on seems to process the Requests node, it is more efficient to only load that node from the database, instead of loading the entire database.
So:
// 👇 Add here
databaseReference.child("Requests").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
// clear old items / users from list to add new data/ users
myItemsList.clear();
// getting all children from users root
// 👇 Remove here
for (DataSnapshot Requests : snapshot.getChildren()) {
...
I am trying to retrieve data from Firebase Realtime Database and add this data to a listview. When I call push() firebase generates two children (one inside the other) with the same unique key. This is the structure of my database:
database
That is how I save the data:
RunningSession runningSession = new RunningSession(date, activityTime, pace, timeElapsed,
finalDistance, image, tipe);
DatabaseReference reference = databaseReference.child("users").child(userUid).child("activities").push();
Map<String, Object> map = new HashMap<>();
map.put(reference.getKey(),runningSession);
reference.updateChildren(map);
This is how i retrieve the data (results in a null pointer exception):
DatabaseReference reference = databaseReference.child("users").child(userId).child("activities");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
list.clear();
for (DataSnapshot snpshot : snapshot.getChildren()) {
RunningSession run = snpshot.getValue(RunningSession.class);
list.add(run);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
ListViewAdapter adapter = new ListViewAdapter(this, list);
ListView activityItems = (ListView) findViewById(R.id.activityList);
activityItems.setAdapter(adapter);
You are getting duplicate push IDs because you are adding them twice to your reference. If you only need one, then simply use the following lines of code:
RunningSession runningSession = new RunningSession(date, activityTime, pace, timeElapsed, finalDistance, image, tipe);
DatabaseReference reference = databaseReference.child("users").child(userUid).child("activities");
String pushedId = reference.push().getKey();
reference.child(pushedId).setValue(runningSession);
The code for reading that may remain unchanged.
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.
So I have a set up where I want to get data from the Database on Firebase, I can get the URL back fine but I can't get data from it. When I try to debug it reads in query.addValueEventListener but after that it just goes straight to my list adapter. I don't know it's not getting the data. I have a similar set up on my node server which works perfectly fine, but won't work on Android for some reason
final DatabaseReference database = FirebaseDatabase.getInstance().getReference();
Query query = database.child("exercises");
Log.i("Database query", database.child("exercises").toString());
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot exerciseSnapshot: dataSnapshot.getChildren())
{
List<ExerciseList> exercises = new ArrayList<ExerciseList>();
ExerciseList exerciseList = exerciseSnapshot.getValue(ExerciseList.class);
Log.i("description/name", exerciseList.getDescription() + " " + exerciseList.getName());
exercises.add(exerciseList);
adapter = new ExerciseLoadListAdapter(mActivity, exercises);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mListView.setAdapter(adapter);
its go to adapter directly because addValueEventListener() is Asynchronous listeners , its run in background !
Try this code , it may be help you !
final DatabaseReference database = FirebaseDatabase.getInstance().getReference();
//Query query = database.child("exercises");
//Log.i("Database query", database.child("exercises").toString());
database.child("exercises").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot exerciseSnapshot: dataSnapshot.getChildren())
{
List<ExerciseList> exercises = new ArrayList<ExerciseList>();
ExerciseList exerciseList = exerciseSnapshot.getValue(ExerciseList.class);
Log.i("description/name", exerciseList.getDescription() + " " + exerciseList.getName());
exercises.add(exerciseList);
}
adapter = new ExerciseLoadListAdapter(mActivity, exercises);
mListView.setAdapter(adapter)
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
The Firebase do NOT get the data in the debug mode in real time. If you need some object from your firebase database you will need to get the object before. I face this problem a several times, thats why in some cases I use SQLite to store data from Firebase before i take any action. But, in a fragment you can not do that, you need the data in realtime. So you need to store the listener in a variable and use it after. Here in my Github page I have a fragment working fine.
ContatsFragment.java
[]'s
My app has two categories of users shop owner and buyers.
When shop owner adds a shop I use his UID and then pushkey to add a shop like this:
In the Image "83yV" and "FmNu" are the shop owners and they add 2 and 1 shop respectively.
Now for buyers, I want to show the whole list of shops in a Recycler View.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_shops_avtivity);
shopsDB = FirebaseDatabase.getInstance().getReference().child("shops");
}
#Override
protected void onStart() {
super.onStart();
FirebaseRecyclerAdapter<Shop,ShopViewHolder> firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Shop, ShopViewHolder>(
Shop.class,
R.layout.shop_single_layout,
ShopViewHolder.class,
shopsDB)
}
The problem is when I point my Firebase Recycler Adapter to shopDB, it returns two empty cardView like this:
When I point it to specific child like
shopsDB = FirebaseDatabase.getInstance().getReference().child("shops").child("83yV24a3AmP15XubhPGApvlU7GE2");
It returns shops add by "83yV".
How can I get the shops added by other owners also? Preferably without altering current DB or creating one DB with all shops in it within on child.
To achieve this, you need to use getChildren() method twice, once to get all the users and second to get all the shops.
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference shopsRef = rootRef.child("shops");
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
for(DataSnapshot dSnapshot : ds.getChildren()) {
String name = dSnapshot.child("name").getValue(String.class);
Log.d("TAG", name);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {}
};
shopsRef.addListenerForSingleValueEvent(eventListener);
Using this code, you'll be able to display all shop names to the buyers.