Android: setReatinInstance(true) not working - android

I am trying to retain fragment when configuration changes but even after using setRetainInstace(true), onDestroy() is called with screen rotation
Here is my fragment code:
public class HelloMoonFragment extends Fragment{
private Button mPlay;
private Button mStop;
private Button mPause;
private AudioPlayer mPlayer = new AudioPlayer();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_hello_moon, null);
mPlay = (Button) v.findViewById(R.id.hellomoon_playButton);
mPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mPlayer.play(view.getContext());
}
});
mStop = (Button) v.findViewById(R.id.hellomoon_stopButton);
mStop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mPlayer.stop();
}
});
mPause = (Button) v.findViewById(R.id.hellomoon_pauseButton);
mPause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
mPlayer.playpause();
}catch (IllegalStateException | NullPointerException ex){
mPlayer.play(getActivity());
}
}
});
return v;
}
#Override
public void onDestroyView() {
super.onDestroyView();
Toast.makeText(getActivity(), "Ikkkkkkkkkkkkkkkkkk", Toast.LENGTH_SHORT);
Log.i("HEYEE", "NOOO");
mPlayer.stop();
}
}
and here is my layout xml file:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/helloMoonFragment"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="com.github.domain.hellomoon.HelloMoonFragment">
</fragment>
Why onDestroy() is called on each rotation?

onDestroy() is not going to be called but in your example, it is:
#Override
public void onDestroyView() {
super.onDestroyView();
Toast.makeText(getActivity(), "Ikkkkkkkkkkkkkkkkkk", Toast.LENGTH_SHORT);
Log.i("HEYEE", "NOOO");
mPlayer.stop();
}
OnDestroy!=onDestroyView
onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onDestroyView Called when the view previously created by onCreateView(LayoutInflater, ViewGroup, Bundle) has been detached from the fragment

This is as designed. onDestroyView() will be called on a configuration change regardless of the setRetainInstance() flag. However, onDestroy() will not be called.
The reason for this is that the Activity in which the fragment is hosted may still be destroyed and re-created -- therefore, any View references in your Fragment will still be holding a reference to that now-dead Activity. So the expected behavior is that any instance state, running tasks, etc. can continue on properly during a configuration change; however, you are still responsible for releasing your View references and re-creating them in onCreateView().

Related

My main fragment code is not being executed

I am writing an android app using android studio. So far I have created a tabbed activity to be my main activity, and another activity that would be opened on a click of a button in one of the tabs in my main tabbed activity. The problem is that I have tried to accomplish this in a few ways but it seems like the code in my public View onCreateView just doesn't get executed at all. This is the code I'm trying to run:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
Button mainSignInButton = (Button) view.findViewById(R.id.mainSignInButton);
mainSignInButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(getActivity() ,LoginActivity.class);
startActivity(i);
}
});
return view;
}
I have also tried to add a simple toast message to this block of code to see if it gets executed but I didnt see the toast pop up as well...
First of all, delegate to Activity the startActivity(). Try something like follow on your fragment:
public class FragmentMain extends Fragment {
private OnInteractionListener mListener;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
Button mainSignInButton = (Button) view.findViewById(R.id.mainSignInButton);
mainSignInButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onClickSignInButton()
}
});
return view;
}
public interface OnInteractionListener {
void onClickSignInButton();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null
}
}
On your Activity try like follow:
public class MyActivity extends AppCompatActivity implements FragmentMain.OnInteractionListener {
...
#Override
public void onClickSignInButton() {
Intent i = new Intent(this, LoginActivity.class);
startActivity(i);
}
}

How to fix black screen when replacing SupportMapFragment

I'm creating a simple application using Mapbox (using mapbox-android-sdk:7.1.0 ) in a fragment.
I have an activity which is composed of a FrameLayout (containing the Fragments) and a button.
At the beginning, the Fragment1 (containing the map) is displayed in the FrameLayout. When the user clicks on the button, the Fragment1 is replaced by the Fragment2 (containing a TextView).
During the transition there is a short black screen.
When I use Mapbox in an activity I don't have any problem, It seems to happen when the method onDestroyView() is called.
MainActivity.java :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Load Fragment1 containing the map
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment1()).commit();
AppCompatButton button = findViewById(R.id.btn_changeFragment);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Load Fragment2 containing a TextView
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment2()).addToBackStack(null).commit();
}
});
}
}
Fragment1.java :
public class Fragment1 extends SupportMapFragment {
private MapView mapView;
public Fragment1(){}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(getContext(), getString(R.string.mapbox_token));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_1, container, false);
mapView = view.findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(#NonNull final MapboxMap mapboxMap) {
mapboxMap.setStyle(Style.LIGHT, new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style) {
// Configure the map
}
});
}
});
return view;
}
#Override
public void onStart() {
super.onStart();
mapView.onStart();
}
#Override
public void onResume() {
super.onResume();
mapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mapView.onPause();
}
#Override
public void onStop() {
super.onStop();
mapView.onStop();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
#Override
public void onDestroyView() {
super.onDestroyView();
mapView.onDestroy();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
}
Using this Attribute app:mapbox_renderTextureMode="true" while loading mapview inside the fragment did the trick for me.
This is a known issue with how MapFragment's behave: https://github.com/mapbox/mapbox-gl-native/issues/9570. Per the ticket, there are currently two possible ways to resolve this issue in your app.
Either:
Use a bitmap of the map in an ImageView while transitioning instead of a MapView. You can then make the MapView visible as part of the OnMapReady callback in your activity.
Or:
Use a TextureView implementation instead. This can be enabled via MapboxMapOptions or .xml attributes. You should also keep in mind that this solution may lead to performance issues down the road.
I tried the first solution proposed by #riastrad but I didnĀ“t find how to make a bitmap of the mapView ( I always had a white screen with the mapbox logo at the bottom).
Anyway, I tried to use setAlpha to change the opacity of the mapView, and it works. I don't realy know why it doesn't work with setVisibility(View.INVISIBLE).
So before to load the Fragment2, I call a method from Fragment1 to set the opacity of the mapView to 0.
MainActivity.java
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if(currentFragment instanceof Fragment1){
// Set the opacity of the mapView to 0
((Fragment1) currentFragment).transition();
// Load Fragment2 containing a TextView
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new Fragment2()).addToBackStack(null).commit();
}else {
getSupportFragmentManager().popBackStack();
}
}
});
Fragment1.java
public void transition(){
mapView.setAlpha(0f);
}

EventBus does not update textview or button and does not show any errors?

I am communicating two fragments of MyActivity when the event is fired from FragmentA (on button click) I want to change in FragmentB button status to enabled true and textview setText("new text") which are in FragmentB, when I run my app output shows no errors but does not make any changes.
here is my fragmentA which fires the event:
Fragment A
...//some code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_a, container, false);
...//more code inside onCreateView
btnChange.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//HERE I POST THE EVENT
EventBus.getDefault().post(new ButtonEvent(true));
// HERE I PUT SOUT TO SHOW IF EVENT IS FIRED
System.out.println("YOU FIRED THE EVENT");// THIS MSG IS SHOWN CORRECTLY
}
});
}
#Subscribe
public void onEvent(ButtonEvent event){
}
#Override
public void onStart() {
super.onStart();
if (!EventBus.getDefault().isRegistered(getActivity())) {
EventBus.getDefault().register(this);
}
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
here is FragmentB
FragmentB extends Fragment{
...//some code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_b, container, false);
btnNuevoMed= (Button) rootView.findViewById(R.id.btnNuevoMed);
btnNuevoMed.setEnabled(false);
txtMed= (TextView)rootView.findViewById(R.id.txtMed);
//...some other code
}
//HERE IS MI SUBSCRIBER
#Subscribe
public void onEvent(ButtonEvent event){
// HERE I PUT A SOUT TO SHOW IF REACH THE METHOD
System.out.println("YOU ARE HEREEEEE"); //BUT NEVER REACH THIS METHOD . WHY?
btnNuevoMed.setEnabled(event.status);
btnNuevoMed.setText("hELLOOO");
txtMed.setText("Modification Success");
}
#Override
public void onStart() {
super.onStart();
if (!EventBus.getDefault().isRegistered(getActivity())) {
EventBus.getDefault().register(this);
}
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
this is my button xml
<TextView
android:id="#+id/txtMed"
android:text="MEDICACION"
style="#style/textoTitulos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"/>
<Button
android:text="Nuevo Boton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/btnNuevoMed"
/>
I'm using eventbus 3.0.0. What is wrong with my code? How can I solve this problem? Why can't I reach the listener method?
Try this:
In Fragment A post event like this
EventBus.getDefault().postSticky(new ButtonEvent(true));
In Fragment B register subscribe like this:
#Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onEvent(ButtonEvent event){
//

What kind of code do i have to put in OnCreate() and when do i have to put it in OnCreateView()?

I am trying to understand when i should use the oncreate method or the oncreateview method.
I am a little bit confused. First i had some code including statements like findViewById() in the OnCreate() method. But it always responded with a null pointer Exception, then someone told me i should put it in the OnCreateView() method. It worked, but i do not understand when and what for code i should put in the OnCreate() or when and what i should put in the OnCreateView(). Could someone please explain this to me.
In my code, the methodology is the following:
ACTIVITY code:
#Override
public void onCreate(Bundle saveInstanceState)
{
super.onCreate(saveInstanceState);
this.setContentView(R.layout.activity_container);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (saveInstanceState == null)
{
getSupportFragmentManager().beginTransaction()
.replace(R.id.activity_container_container, new MyFragment()).addToBackStack(null).commit();
}
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
}
#Override
public void myInterface()
{
System.out.println("Do stuff;");
}
FRAGMENT code:
private int titleId;
private Button placeholderButton;
private MyInterface activity_myInterface;
public MyFragment()
{
super();
this.titleId = R.string.my_actionbar_title;
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_myInterface = (MyInterface)activity;
}
catch(ClassCastException e)
{
Log.e(this.getClass().getSimpleName(), "MyInterface interface needs to be implemented by Activity.", e);
throw e;
}
}
//this is an interface defined by me
#Override
public int getActionBarTitleId()
{
return titleId;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_myfragment, container, false);
placeholderButton = (Button)view.findViewById(R.id.myfragment_placeholderbtn);
placeholderButton.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v)
{
if(v == placeholderButton)
{
System.out.println("Do more stuff");
}
}
Where R.layout.activity_container is:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/activity_container_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
This way the Activity is responsible for containing the Fragment (and this fragment is created in onCreate()) and the Fragment is responsible for the display of UI and the event handling. You can have more than one fragment on an Activity, though, that's what they primarily were designed for.
If you are using an Activity, then you can just stay away from the onCreateView.
Just stick to the default activity lifecycle:
The layout (content) is inflated (created) at the onCreate method.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
}
Then once it has been inflated you are ready to use it and manipulate views that belong to it.
findViewById() returned null for you because the layout was not ready to be used and that's because it was not yet inflated. You can use findViewById() after setContentView() in onCreate.
findViewById() is a method that can be pretty hard on the performance (if called many times), so you should get all the views you need in onCreate and save them to an instance variable like:
TextView textView1, textView2;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
textView1 = findViewById(R.id.textview1);
textView2 = findViewById(R.id.textview2);
...
}
And use them from onStart:
#Override
protected void onStart() {
super.onStart();
if (textView1 != null) textView1.setText("myText");
if (textView2 != null) textView2.setText("some other text");
}

Loader is not retained after starting new activity, changing orientation in new activity and pressing back button

I been running into an issue with loaders lately. I created a small project that reproduces the issue https://github.com/solcott/loaders-orientation-change
I have a simple Activity that adds a fragment
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
if(mainFragment == null){
Log.w(getClass().getSimpleName(), "creating new Fragment");
mainFragment = new MainFragment();
getFragmentManager().beginTransaction().add(R.id.main_fragment, mainFragment).commit();
}
}
}
In my fragment I start a Loader that just returns an integer that is displayed on the screen. There is also a button that starts a new Activity:
public class MainFragment extends Fragment implements LoaderCallbacks<Integer> {
private static int NEXT_VAL = 0;
TextView text1;
Button button1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(1, null, this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
text1 = (TextView) view.findViewById(android.R.id.text1);
button1 = (Button) view.findViewById(android.R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getActivity(), NextActivity.class));
}
});
}
#Override
public Loader<Integer> onCreateLoader(int id, Bundle args) {
AsyncTaskLoader<Integer> loader = new AsyncTaskLoader<Integer>(
getActivity()) {
#Override
public Integer loadInBackground() {
return NEXT_VAL++;
}
};
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader<Integer> loader, Integer data) {
text1.setText(data.toString());
}
#Override
public void onLoaderReset(Loader<Integer> loader) {
// TODO Auto-generated method stub
}
}
Up to this point everything works fine I can change the orientation and the integer that is displayed doesn't change. I can even start the new activity and then hit the back button and the integer displayed doesn't change.
However, when I navigate to the new Activity, change orientation and press the back button the integer is incremented. I would expect it to behave the same way that it did without the orientation change.
Calling setRetainInstance(true) in Fragment.onCreate() make it even worse. onLoadComplete is never called and the integer is not displayed at all.
Has anyone else run into this issue and found a workaround?
Same question as asked here: Loader unable to retain itself during certain configuration change
This is a damn annoying problem with AsyncTask loaders. Although a solution is proposed in that thread, I think the general consensus is to avoid AsyncTaskLoaders.

Categories

Resources