Android: my app memory over 300MB of ram just on idle - android

i noticed that my app uses alot of memory so i used the profiler and found that as soon as the app starts and splash screen appear the app get over 150+MB even on splash before opening main or anyother activity.
Update: i solved all memory leaks, yet my app still my app uses on my real phone nearly 300+ MB of ram just while browsing the MainActivity with recycleview.
LeakCanary and i keeps giving me error that this is a cause of memory leaks so anyone please tell me how to solve it >
if you clicked any recyclerview items:
[]
Activity Code:
public class WorkDetailsActivity extends AppCompatActivity {
ArrayList<String> imagesFromURL = new ArrayList<String>();
ActivityWorkDetailsBinding binding;
DatabaseReference databaseReference;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWorkDetailsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
String workUid_details = getIntent().getExtras().getString("UID_Details");
String title = getIntent().getExtras().getString("name");
String description = getIntent().getExtras().getString("description");
String location = getIntent().getExtras().getString("location");
String path = getIntent().getExtras().getString("path");
databaseReference = FirebaseDatabase.getInstance().getReference().child("Work").child(path);
databaseReference.child(workUid_details).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
binding.workDetailsTitle.setText(title);
binding.workDetailsDescription.setText(description);
binding.workDetailsLocation.setText(location);
binding.getUIDDetails.setText(workUid_details);
for (DataSnapshot dataSnapshot : snapshot.child("images").getChildren()) {
String value = String.valueOf(dataSnapshot.child("image").getValue());
imagesFromURL.add(value);
//Log.i("Value", String.valueOf(imagesFromURL));
}
initRecyclerView();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
}
private void initRecyclerView(){
binding.workDetailsImage.setNestedScrollingEnabled(false);
binding.workDetailsImage.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
binding.workDetailsImage.setHasFixedSize(true);
PrivateRecyclerAdapter adapter = new PrivateRecyclerAdapter(this, imagesFromURL);
binding.workDetailsImage.setAdapter(adapter);
binding.progressBar.setVisibility(View.VISIBLE);
/* //Add Divider between recyclerView items
DividerItemDecoration itemDecorator = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL);
binding.workDetailsImage.addItemDecoration(itemDecorator);
final int radius = getResources().getDimensionPixelSize(R.dimen.radius);
final int dotsHeight = getResources().getDimensionPixelSize(R.dimen.dots_height);
final int color = ContextCompat.getColor(this, R.color.green);
binding.workDetailsImage.addItemDecoration(new DotsIndicatorDecoration(radius, radius * 2, dotsHeight, color, color));
binding.workDetailsImage.setOnFlingListener(null);
new PagerSnapHelper().attachToRecyclerView(binding.workDetailsImage);*/
}
#Override
protected void onDestroy() {
super.onDestroy();
imagesFromURL = null;
binding.workDetailsImage.setAdapter(null);
binding.workDetailsTitle.setText(null);
binding.workDetailsLocation.setText(null);
binding.workDetailsDescription.setText(null);
binding.getUIDDetails.setText(null);
binding.workDetailsImage.setAdapter(null);
}

The problem with the above code is that you are registering value event listener as anonymous implementation. Which will hold the reference of the activity. And based on the LeakCanary stack trace your activity was in destroyed state but due to the listener the activity instance is not being able to be garbage collected and hence it is being leaked. What you need to do is add and remove the listener as below.
Create the instance of ValueEventListener and store it in variable
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
binding.workDetailsTitle.setText(title);
binding.workDetailsDescription.setText(description);
binding.workDetailsLocation.setText(location);
binding.getUIDDetails.setText(workUid_details);
for (DataSnapshot dataSnapshot : snapshot.child("images").getChildren()) {
String value = String.valueOf(dataSnapshot.child("image").getValue());
imagesFromURL.add(value);
//Log.i("Value", String.valueOf(imagesFromURL));
}
initRecyclerView();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
Register it in the onStart method of your activity lifecycle using
#Override
protected void onStart() {
databaseReference.child(workUid_details).addValueEventListener(valueEventListener);
}
Remove the listener in onStop method of your activity lifecycle using
#Override
protected void onStop() {
databaseReference.child(workUid_details).removeEventListener(valueEventListener);
}

Related

Device rotation maintaining state

I'm aware that when you rotate a device, the view and activities are destroyed, so a new instance of both activity and fragment are created.
I pass into a fragment a matchid, which I'm presuming is also destoryed.
Code below, this is ran in a fragment (I have tried this in the onCreateView and also in the onViewCreated methods);
String getArgument = getArguments().getString("matchid");
DatabaseReference database = FirebaseDatabase.getInstance().getReference();
DatabaseReference ref = database.child("Matches");
Query gameQuery = ref.orderByChild("gameID").equalTo(getArgument);
gameQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot singleSnapshot : dataSnapshot.getChildren()){
TextView homeTeamSetTxt = (TextView) getView().findViewById(R.id.txtRefHomeTeam);
My question is how do you maintain state whilst rotating a device?
I don't want to lock the user to either orientation.
You can override
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("matchid", matchid);
}
and when the screen is recreated
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
dataGotFromServer = savedInstanceState.getString("matchid");
}
Or you can use viewmodels which survives device rotations.
Hope that helpes

Recyclerview is not displaying anything, any ideas? Using FirestoreUI RecyclerAdapter

The code Below I seem to have done everything needed, but the recyclerview is just not being populated.
I even put the Log on OnDateChange to see if it's called, and it was called once on the LogCat after I ran it on my Galaxy S6. Also, the database is public.
RecyclerView chat_view;
EditText message_text;
CollectionReference reference = FirebaseFirestore.getInstance().collection("stage2");
Query query = reference.orderBy("timestamp");
FirestoreRecyclerAdapter<ChatMessage,ChatHolder> adapter=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
//initializing, called below
init();
}
private FirestoreRecyclerAdapter<ChatMessage,ChatHolder> newAdapter() {
final FirestoreRecyclerOptions options = new FirestoreRecyclerOptions.Builder<ChatMessage>()
.setQuery(query,ChatMessage.class)
.setLifecycleOwner(this)
.build();
return new FirestoreRecyclerAdapter<ChatMessage, ChatHolder>(options) {
#Override
public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_item,parent,false);
//if(((ChatMessage)options.getSnapshots().getSnapshot(viewType)).)
return new ChatHolder(view);
}
#Override
protected void onBindViewHolder(ChatHolder holder, int position, ChatMessage model) {
//displaying the message and users into the recyclerview
holder.user_msg.setText(model.getMessage());
holder.user_date.setText(model.getDate());
holder.user_name.setText(model.getName());
}
#Override
public void onDataChanged() {
Log.d("TAG", "YESSIR");
}
#Override
public void onError(FirebaseFirestoreException e) {
super.onError(e);
}
};
}
private void init(){
chat_view = (RecyclerView) findViewById(R.id.chat_view);
chat_view.setVerticalScrollBarEnabled(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
chat_view.setLayoutManager(layoutManager);
adapter = newAdapter();
chat_view.setAdapter(adapter);
message_text = (EditText) findViewById(R.id.message_text);
message_text.setEnabled(appUtils.isSignedIn());
findViewById(R.id.send_button).setEnabled(appUtils.isSignedIn());
}
So, there we have it, I have no idea what the problem could be at this point. I am using the most recent Firestore UI dependency as well.
It seems that you have done everything necessary..using FirestoreRecycleroptions and inflating the layout in onCreateViewHolder.
For FirestoreRecyclerAdapter to be able to have the data on the activity, then you need to listen to any changes in onStart() which is after onCreate(). Also
in onStop you can stop listening.
#Override
public void onStart() {
super.onStart();
adapter.startListening();
}
#Override
public void onStop() {
super.onStop();
adapter.stopListening();
}

Android fetching values from Firebase Database

Hello so I've been strugging for awhile now. I want to consult someone about my code. I want to apply
a coloring function to my app.
When the person presses the button when it's at its:
GREEN state it updates the value on the database to BEING HOUSEKEPT
YELLOW state when pressed sends READY FOR INSPECTION RoomStatus
RED state when pressed sends READY FOR HOUSEKEEPING RoomStatus
It was working earlier but when I tried to restrict the users that users who are Housekeepers can't access the RED STATE which are for House Keepers, I inserted somewhere in my code where I want to implement it.
I'm going over loops here can somebody tell me where I did wrong?
Here's my code:
Button room1;
private DatabaseReference mFirebaseDatabase, mFirebaseDatabase1, mFirebaseDatabase1room;
private FirebaseDatabase mFirebaseInstance;
private DatabaseReference referenceroom1;
private String roomStat;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navi_to_scan2);
mFirebaseInstance = FirebaseDatabase.getInstance();
mFirebaseDatabase1 = mFirebaseInstance.getReference("Rooms");
mFirebaseDatabase = mFirebaseInstance.getReference("Users");
mFirebaseDatabase1room = mFirebaseInstance.getReference("Rooms").child("Room1");
referenceroom1 = mFirebaseDatabase1.child("Room1").child("RoomStatus");
room1 = (Button) findViewById(R.id.rm1Btn);
mFirebaseDatabase1.child("Room1").addValueEventListener(new ValueEventListener() { //attach listener
#Override
public void onDataChange(DataSnapshot dataSnapshot) { //something changed!
for (DataSnapshot locationSnapshot : dataSnapshot.getChildren()) {
String location = locationSnapshot.getValue().toString();
if (location.equals("READY FOR HOUSEKEEPING")) {
room1.setBackgroundColor(Color.GREEN);
roomStat = "Green";
} else if (location.equals("BEING HOUSEKEPT")) {
room1.setBackgroundColor(Color.YELLOW);
roomStat = "Yellow";
} else {
room1.setBackgroundColor(Color.RED);
roomStat = "Red";
}
if (roomStat.equals("Green")) {
room1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = "BEING HOUSEKEPT";
DatabaseReference reference = mFirebaseDatabase1.child("Room1").child("RoomStatus");
reference.setValue(message);
Intent next1 = new Intent(getApplicationContext(), ReaderActivity3.class);
startActivity(next1);
}
});
} else if (roomStat.equals("Yellow")) {
room1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = "READY FOR INSPECTION";
DatabaseReference reference = mFirebaseDatabase1.child("Room1").child("RoomStatus");
reference.setValue(message);
Intent next1 = new Intent(getApplicationContext(), ReaderActivity2.class);
startActivity(next1);
}
});
} else {
room1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = "READY FOR HOUSEKEEPING";
DatabaseReference reference = mFirebaseDatabase1.child("Room1").child("RoomStatus");
reference.setValue(message);
Intent next1 = new Intent(getApplicationContext(), ReaderActivity.class);
startActivity(next1);
//I ALSO WANT TO PUT A CONDITION HERE FETCHING userType from Structure
// mFirebaseDatabase = mFirebaseInstance.getReference("Users").child("userKey");
//but doing this would mean that I would have to put a listener inside, would that be okay? I tried when this
//was working at first but it didin't and now the whole thing is not working
}
});
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) { //update UI here if error occurred.
}
});
}
Here's my structure:
Can you help me identify what's the problem with how i implemented this?
I see nothing wrong, try debugging. Android is buggy nowadays, especially when you're connecting to a cloud database. In the left side of the run button the bug shaped one. Debug.
Or maybe you could try to show your location which is your string, to check if you pulled the right info from your database. You can use logs. A guide can be found here
https://developer.android.com/studio/debug/am-logcat.html

Firebase Database retrieve data Issue

I have put a button in my App. When I press the button +1 value saves to my database. So I want to check a method that when the button count =5 show an alert.It checks when i click the button. When my button count =5 alert shows 3-4 times. But I want to show it for 1 time :(
Here is the code :
private void ButtonChecker(){
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ezzeearnRef = rootRef.child(User1);
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Long BRef1 = dataSnapshot.child("BRef").getValue(Long.class);
assert BRef1 != null;
int x = BRef1.intValue( );
ref = x;
if (x ==5){
showAlert("Don't CLick Button ");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
ezzeearnRef.addValueEventListener(eventListener);
}
You're registering a new listener every time that the user presses the button. If the initial value is 3, the user has to press the button twice to get it to 5. That means your code registers two listeners and thus shows two alerts.
Two solutions:
Only register a listener once.
Register listeners that only get the current value.
Register listeners that only get the current value
This is the simplest change: we'll register a listener that:
Reads the current value
Automatically unregisters itself
The only change is how you register the listener:
ezzeearnRef.addListenerForSingleValueEvent(eventListener);
But this still registers/unregisters a listener for every click, which can be a bit wasteful. In addition: if you ever make your app multi-user, this misses the fact that other users may be incrementing the counter too.
Only register a listener once
Whenever possible, register and unregister your listeners in activity-lifecycle methods. For example, it is quite common to register the listeners in onStart and unregister them in onStop:
#Override
protected void onStart() {
super.onStart();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ezzeearnRef = rootRef.child(User1);
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Long BRef1 = dataSnapshot.child("BRef").getValue(Long.class);
assert BRef1 != null;
int x = BRef1.intValue( );
ref = x;
if (x ==5){
showAlert("Don't CLick Button ");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
};
ezzeearnRef.addValueEventListener(eventListener);
}
#Override
protected void onStop() {
super.onStop();
ezzeearnRef.removeEventListener(eventListener);
}
With this code your listener will be active during the lifecycle of the activity. During that time, if the counter (is or) becomes 5, the alert will show once.
Sometime firebase call for data multiple time so you will get same x value more then one time. to fix this use a boolean variable which store value about alert.
boolean isAlertShown = false;
private void ButtonChecker(){
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference ezzeearnRef = rootRef.child(User1);
ValueEventListener eventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Long BRef1 = dataSnapshot.child("BRef").getValue(Long.class);
assert BRef1 != null;
int x = BRef1.intValue( );
ref = x;
if (x ==5 && !isAlertShown){
isAlertShown = true;
showAlert("Don't CLick Button ");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
ezzeearnRef.addValueEventListener(eventListener);
}

RecyclerView Duplicating On Back Press

I load a recyclerview based on Firebase data via the following method:
#Override
public void onStart() {
super.onStart();
mChildEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String newPollEpoch = dataSnapshot.getKey();
if (mNewPollsAray.contains(newPollEpoch)) {
Log.v("POLL_ADDED", "POLL ADDED: " + newPollEpoch);
} else {
Log.v("Child_Added", "The new child is " + newPollEpoch);
String newPollImageURL = dataSnapshot.child(IMAGE_URL).getValue(String.class);
//TODO: On additional devices, numbesr are not appearing as the question
String newPollQuestion = dataSnapshot.child(QUESTION_STRING).getValue(String.class);
String convertedQuestion = newPollQuestion.toString();
mNewPollsAray.add(0, new Poll(convertedQuestion, newPollImageURL, newPollEpoch));
mNewPollsAdapter.notifyDataSetChanged();
Log.v("OnChildChanged", "OnCHILDCHANGEDCALLED " + dataSnapshot.getKey());
}
}
#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) {
}
};
mPollsRef.addChildEventListener(mChildEventListener);
}
#Override
public void onStop() {
super.onStop();
mPollsRef.removeEventListener(mChildEventListener);
}
Here is the method I call when an item in the recyclerview is clicked:
#Override
public void onClick(View view) {
view.getId();
int itemPosition = getAdapterPosition();
String passEpoch = mNewPollsAray.get(itemPosition).getPollID();
Log.v("PASSED_ID", "The passed ID is " + passEpoch);
Intent toPoll = new Intent(getActivity(), PollHostActivity.class);
toPoll.putExtra("POLL_ID", passEpoch);
startActivity(toPoll);
}
The fragment I am loading it from is part of a TabLayout. When I navigate between the tabs the recyclerview loads correctly.
However, when I click an item in the recyclerview (which takes me to a new activity) and then navigate back to the fragment containing the recyclerview, items get duplicated and the recyclerview items are all out of order. I think it has to do with onStart() being called multiple times and essentially "stacking" new items onto the recyclerview instead of replacing them, but I was hoping to confirm.
This happens because you add a listener, but never remove it. So the next time when you enter the view, you add a second listener and thus get two calls to onChildAdded() for each item in the database.
The solution is to remove the listener when you exit the view. Since you attach the listener in onStart(), you should remove it again in onStop():
#Override
public void onStop() {
mPollsRef.removeEventListener(mChildEventListener);
}
You can try with code, I was facing similar issue got resolved with bellow changes.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mMainLayout == null)
{
mMainLayout = inflater.inflate(R.layout.fragment_main, container,false);
...
}
return mMainLayout;
}
When mMainlayout is not null, it mean that your fragment instance has already one instance of the mMainLayout and already added to ViewGroup container no need to add it again. You may be facing issue as you are adding same view again to same container.
By Clear the data set you can avoid loading of similar items again in Recycler View. It worked for me.
listOftrailers.clear();
try {
JSONObject jsonObject = new JSONObject(data);
JSONArray jsonArray = jsonObject.getJSONArray("results");
for (int i = 0; i < jsonArray.length(); i++) {
MovieTrailer item = new MovieTrailer();
JSONObject js = jsonArray.getJSONObject(i);
item.setVideoID(js.getString("id"));
item.setVideoName(js.getString("name"));
item.setVideoKey(js.getString("key"));
item.setVideoSite(js.getString("site"));
item.setVideoType(js.getString("type"));
String name = item.getVideoName();
if (name.contains("Official Trailer") ||
name.startsWith("Official"))
listOftrailers.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
videosadapter = new TrailerListAdapter(listOftrailers.size(),
listOftrailers, MoviePage.this);
recyclerView.setAdapter(videosadapter);

Categories

Resources