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
Related
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);
}
I have a RecyclerView with MyItem as holding the data for each rows in the RecyclerView, and everything is properly implemented and working fine.
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{
protected final List<MyItem> items= new ArrayList<>();
public MyAdapter (List<RVItem> list) {
if (list != null) {
items.addAll(list);
}
}
}
And the List items is passed from another object.
public class MyObject{
private String otherThing;
private List<MyItem> items;
public someMethodToInitItems() {
items = new ArrayList<>();
items.add(...);
items.add(...);
}
public createFragment() {
Fragment f = new MyCustomFragment();
f.setAdapter(new MyAdapter(items));
// replace commit fragment...
}
}
The problem here is when my Activity is restored, the field private List<MyItem> items in MyObject somehow magically get restored too, but field like otherThing is still null and not getting restored.
FYI, I did not explicitly save and restore items in any way. Also, onSaveInstanceState in MyCustomFragment is left untouched and not overridden from super.
Below is how I'm trying to save and restore the Fragment state from the Activity. That's it.
public class MyActivity{
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, tag, getLastFragment());
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
Fragment fg = getSupportFragmentManager().getFragment(savedInstanceState, tag);
}
}
}
So, I don't get it. Is Android system did this job? Save and restore the field items in MyObject automatically? Or there must be something else causing this behavior.
You need to override onRetainNonConfigurationInstance method. This method could helps you to store your data
More details here
I am trying to get Firebase database but one fragment can get the data but the other fragment can't even call onDataChange. I was guessing if DB is opened in the other fragment maybe I can't open it again. It is just my guess.. if my guess is correct could you please let me know how to close it or how to fix this issue? The same code works in the other fragment and the other fragment is the first called fragment so I think DB structure has no issue.
This is not working code. I saw someone saying put the reading DB code in onViewCreated instead of onCreateView but still not working.
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_about, container, false);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView textView = view.findViewById(R.id.txtSlogan);
Button payNow = view.findViewById(R.id.btnPay);
Myuser_pay = new UserModel();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
FirebaseDatabase.getInstance().getReference().child("users").child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
Myuser_pay = dataSnapshot.getValue(UserModel.class);
paiduser = Myuser_pay.paid;
Toast.makeText(getActivity(),"OK",Toast.LENGTH_LONG);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(getActivity(),"Error",Toast.LENGTH_LONG);
}
});
if (paiduser.equals("true")){
textView.setText("Thank you for your payment. Your account is now fully activated");
}
payNow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
processPayment();
}
});
}
Found answer after long debugging. It looks like this id due to my for loop which is using DB in recycler adapter. What I did was I created static class which can hold user data when user login and using that saved data instead of reading DB again. This simple fix could solve the issue.
https://medium.com/#cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf says to restore state in the model instead of the presenter. What if I have a very simple "model", say a binary toggle that updates a textview to be on or off? Creating a model Toggle class that has a single string value seems like overkill.
Another option is to pass the bundle from my Activity into a corresponding method in my presenter inside onSaveInstanceState and restore it similarly with onCreate. But the article also says that we should avoid having android dependencies in the presenter.
Finally I tried using Icepick but this did not work:
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
(Button) findViewById(R.id.btn).setOnClickListener(this);
presenter.onCreate();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
#Override
public void onClick(View view) {
presenter.onButtonClicked();
}
#Override
public void updateState(String state) {
tv.setText(state);
}
MainPresenter.java
public class MainPresenter {
private MainView mainView;
#State String toggle;
#Inject
public MainPresenter(MainView mainView) {
this.mainView = mainView;
}
void onCreate() {
mainView.updateState(toggle);
}
void onButtonClicked() {
mainView.updateState(toggle.equals("on") ? "off" : "on");
}
}
What are my options? If I have to use the model approach can I see an example of this for my case?
If you're using annotation processing to maintain the state, it won't automatically populate data into your presenter without Icepick.saveInstanceState(this, outState) which you can't call in the presenter.
#State String toggle;
This line should be present in the activity. Have a method in your presenter to request data by toggle. Something like this:
#State String toggle = "off"; //default value
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
(Button) findViewById(R.id.btn).setOnClickListener(this);
presenter.onCreate();
presenter.setState(toggle)
}
You can store this value as the global variable in the presenter and decide the app flow accordingly.
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);
}