Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragment inner class should be static

I have a FragmentActivity class with inner class that should display Dialog. But I am required to make it static. Eclipse offers me to suppress error with @SuppressLint("ValidFragment"). Is it bad style if I do it and what are the possible consequences?

public class CarActivity extends FragmentActivity { //Code   @SuppressLint("ValidFragment")   public class NetworkConnectionError extends DialogFragment {     private String message;     private AsyncTask task;     private String taskMessage;     @Override     public void setArguments(Bundle args) {       super.setArguments(args);       message = args.getString("message");     }     public void setTask(CarActivity.CarInfo task, String msg) {       this.task = task;       this.taskMessage = msg;     }     @Override     public Dialog onCreateDialog(Bundle savedInstanceState) {       // Use the Builder class for convenient dialog construction       AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());       builder.setMessage(message).setPositiveButton("Go back", new DialogInterface.OnClickListener() {         @Override         public void onClick(DialogInterface dialog, int id) {           Intent i = new Intent(getActivity().getBaseContext(), MainScreen.class);           startActivity(i);         }       });       builder.setNegativeButton("Retry", new DialogInterface.OnClickListener() {         @Override         public void onClick(DialogInterface dialog, int id) {           startDownload();         }       });       // Create the AlertDialog object and return it       return builder.create();     }   } 

startDownload() starts Asynctask.

like image 716
user2176737 Avatar asked Mar 22 '13 13:03

user2176737


People also ask

Can inner class have static members?

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.

Can Java have non-static inner class?

A non-static nested class is a class within another class. It has access to members of the enclosing class (outer class). It is commonly known as inner class . Since the inner class exists within the outer class, you must instantiate the outer class first, in order to instantiate the inner class.

What are the rules for inner class?

Rules of Local Inner Class:The scope of the local inner class is restricted to the block they are defined in. A local inner class cannot be instantiated from outside the block where it is created in. Till JDK 7, the Local inner class can access only the final local variable of the enclosing block.

Can inner class be protected?

protected Inner Class There is one more particular case — a protected inner class. As we can see, this is a static inner class, and so can be constructed from outside of an instance of FirstClass. However, as it is protected, we can only instantiate it from code in the same package as FirstClass.


2 Answers

Non static inner classes do hold a reference to their parent classes. The problem with making a Fragment inner class non-static is that you always hold a reference to the Activity. The GarbageCollector cannot collect your Activity. So you can 'leak' the Activity if for example the orientation changes. Because the Fragment might still live and gets inserted in a new Activity.

EDIT:

Since some people asked me for some example I started writing one, while doing this I found some more problems when using non static Fragments:

  • They cannot be used in a xml file since they do not have a empty constructor (They can have an empty constructor, but you usually instantiate nonstatic nested classes by doing myActivityInstance.new Fragment() and this is different to only calling an empty constructor)
  • They cannot be reused at all - since the FragmentManager sometimes calls this empty constructor too. If you added the Fragment in some Transaction.

So in order to make my example work I had to add the

wrongFragment.setRetainInstance(true); 

Line to not make the app crash on orientation change.

If you execute this code you will have an activity with some textviews and 2 buttons - the buttons increase some counter. And the Fragments show the orientation which they think their activity has. At the start everything works correctly. But after changing the screen orientation only the first Fragment works correcly - the second one is still calling stuff at its old activity.

My Activity class:

package com.example.fragmenttest;  import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.res.Configuration; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView;  public class WrongFragmentUsageActivity extends Activity { private String mActivityOrientation=""; private int mButtonClicks=0; private TextView mClickTextView;   private static final String WRONG_FRAGMENT_TAG = "WrongFragment" ;  @Override protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     int orientation = getResources().getConfiguration().orientation;     if (orientation == Configuration.ORIENTATION_LANDSCAPE)     {         mActivityOrientation = "Landscape";     }     else if (orientation == Configuration.ORIENTATION_PORTRAIT)     {         mActivityOrientation = "Portrait";     }      setContentView(R.layout.activity_wrong_fragement_usage);     mClickTextView = (TextView) findViewById(R.id.clicksText);     updateClickTextView();     TextView orientationtextView = (TextView) findViewById(R.id.orientationText);     orientationtextView.setText("Activity orientation is: " + mActivityOrientation);      Fragment wrongFragment = (WrongFragment) getFragmentManager().findFragmentByTag(WRONG_FRAGMENT_TAG);     if (wrongFragment == null)     {         wrongFragment = new WrongFragment();         FragmentTransaction ft = getFragmentManager().beginTransaction();         ft.add(R.id.mainView, wrongFragment, WRONG_FRAGMENT_TAG);         ft.commit();         wrongFragment.setRetainInstance(true); // <-- this is important - otherwise the fragment manager will crash when readding the fragment     } }  private void updateClickTextView() {     mClickTextView.setText("The buttons have been pressed " + mButtonClicks + " times"); }  private String getActivityOrientationString() {     return mActivityOrientation; }   @SuppressLint("ValidFragment") public class WrongFragment extends Fragment {       @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)     {         LinearLayout result = new LinearLayout(WrongFragmentUsageActivity.this);         result.setOrientation(LinearLayout.VERTICAL);         Button b = new Button(WrongFragmentUsageActivity.this);         b.setText("WrongFragmentButton");         result.addView(b);         b.setOnClickListener(new View.OnClickListener()         {             @Override             public void onClick(View v)             {                 buttonPressed();             }         });         TextView orientationText = new TextView(WrongFragmentUsageActivity.this);         orientationText.setText("WrongFragment Activities Orientation: " + getActivityOrientationString());         result.addView(orientationText);         return result;     } }  public static class CorrectFragment extends Fragment {     private WrongFragmentUsageActivity mActivity;       @Override     public void onAttach(Activity activity)     {         if (activity instanceof WrongFragmentUsageActivity)         {             mActivity = (WrongFragmentUsageActivity) activity;         }         super.onAttach(activity);     }      @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)     {         LinearLayout result = new LinearLayout(mActivity);         result.setOrientation(LinearLayout.VERTICAL);         Button b = new Button(mActivity);         b.setText("CorrectFragmentButton");         result.addView(b);         b.setOnClickListener(new View.OnClickListener()         {             @Override             public void onClick(View v)             {                 mActivity.buttonPressed();             }         });         TextView orientationText = new TextView(mActivity);         orientationText.setText("CorrectFragment Activities Orientation: " + mActivity.getActivityOrientationString());         result.addView(orientationText);         return result;     } }  public void buttonPressed() {     mButtonClicks++;     updateClickTextView(); }  } 

Note that you should probably not cast the activity in onAttach if you want to use your Fragment in different Activities - but for here its working for the example.

The activity_wrong_fragement_usage.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".WrongFragmentUsageActivity"  android:id="@+id/mainView">  <TextView     android:id="@+id/orientationText"     android:layout_width="wrap_content"     android:layout_height="wrap_content"     android:text="" />  <TextView     android:id="@+id/clicksText"     android:layout_width="wrap_content"     android:layout_height="wrap_content"     android:text="" />    <fragment class="com.example.fragmenttest.WrongFragmentUsageActivity$CorrectFragment"           android:id="@+id/correctfragment"           android:layout_width="wrap_content"           android:layout_height="wrap_content" />   </LinearLayout> 
like image 114
Simon Meyer Avatar answered Nov 07 '22 01:11

Simon Meyer


I won't talk about inner fragments, but more specifically about a DialogFragment defined within an activity because it's 99% of the case for this question.
From my point of view, I don't want my DialogFragment (your NetworkConnectionError) to be static because I want to be able to call variables or methods from my containing class (Activity) in it.
It won't be static, but I don't want to generate memoryLeaks either.
What is the solution?
Simple. When you go in onStop, ensure you kill your DialogFragment. It's as simple as that. The code looks like something like that:

public class CarActivity extends AppCompatActivity{  /**  * The DialogFragment networkConnectionErrorDialog   */ private NetworkConnectionError  networkConnectionErrorDialog ; //...  your code ...// @Override protected void onStop() {     super.onStop();     //invalidate the DialogFragment to avoid stupid memory leak     if (networkConnectionErrorDialog != null) {         if (networkConnectionErrorDialog .isVisible()) {             networkConnectionErrorDialog .dismiss();         }         networkConnectionErrorDialog = null;     } } /**  * The method called to display your dialogFragment  */ private void onDeleteCurrentCity(){     FragmentManager fm = getSupportFragmentManager();      networkConnectionErrorDialog =(DeleteAlert)fm.findFragmentByTag("networkError");     if(networkConnectionErrorDialog ==null){         networkConnectionErrorDialog =new DeleteAlert();     }     networkConnectionErrorDialog .show(getSupportFragmentManager(), "networkError"); } 

And that way you avoid memory leaks (because it's bad) and you insure you don't have a [expletive] static fragment that cannot access your activity's fields and methods. This is the good way to handle that problem, from my point of view.

like image 39
Mathias Seguy Android2ee Avatar answered Nov 07 '22 02:11

Mathias Seguy Android2ee