69 votes

La classe interne du fragment doit être statique

J'ai un FragmentActivity avec une classe interne qui doit afficher Dialog . Mais je suis obligé de le faire static . Eclipse me propose de supprimer l'erreur avec @SuppressLint("ValidFragment") . Est-ce un mauvais style si je le fais et quelles sont les conséquences possibles ?

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() démarre Asynctask.

93voto

Simon Meyer Points 1050

Classes internes non statiques contiennent une référence à leurs classes parentes. Le problème qui se pose lorsqu'on rend non statique une classe interne de Fragment, c'est qu'on conserve toujours une référence à l'élément Activité . Le site Collecteur d'ordures ne peut pas collecter votre Activité . Vous pouvez donc "fuir" le Activité si par exemple l'orientation change. Parce que le Fragment pourrait encore vivre et être insérée dans une nouvelle Activité .

EDITAR:

Comme certaines personnes m'ont demandé un exemple, j'ai commencé à en écrire un. En faisant cela, j'ai découvert d'autres problèmes liés à l'utilisation de fragments non statiques :

  • Elles ne peuvent pas être utilisées dans un fichier xml car elles n'ont pas de constructeur vide (elles peuvent avoir un constructeur vide, mais vous instanciez généralement les classes imbriquées non statiques en faisant myActivityInstance.new Fragment() et c'est différent d'appeler seulement un constructeur vide)
  • Ils ne peuvent pas du tout être réutilisés - puisque l FragmentManager appelle aussi parfois ce constructeur vide. Si vous avez ajouté le Fragment dans certaines Transactions.

Pour que mon exemple fonctionne, j'ai donc dû ajouter l'option

wrongFragment.setRetainInstance(true);

Ligne pour ne pas faire planter l'application lors d'un changement d'orientation.

Si vous exécutez ce code, vous aurez une activité avec des vues de texte et 2 boutons - les boutons augmentent un compteur. Et les Fragments montrent l'orientation qu'ils pensent que leur activité a. Au début, tout fonctionne correctement. Mais après avoir changé l'orientation de l'écran, seul le premier fragment fonctionne correctement - le second appelle toujours des choses dans son ancienne activité.

Ma classe d'activité :

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();
}

}

Notez que vous ne devriez probablement pas lancer l'activité en onAttach si vous voulez utiliser votre Fragment dans des activités différentes - mais ici, cela fonctionne pour l'exemple.

L'activité_mauvaise_fragmentation_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>

18voto

user2019088 Points 91

Je ne parlerai pas des fragments internes, mais plus précisément d'un DialogFragment défini au sein d'une activité, car c'est le cas dans 99 % des cas pour cette question.
De mon point de vue, je ne veux pas que mon DialogFragment (votre NetworkConnectionError) soit statique car je veux pouvoir y appeler des variables ou des méthodes de la classe qui le contient (Activity).
Il ne sera pas statique, mais je ne veux pas non plus générer de memoryLeaks.
Quelle est la solution ?
Simple. Quand tu vas dans onStop, assure-toi de tuer ton DialogFragment. C'est aussi simple que ça. Le code ressemble à quelque chose comme ça :

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");
}

De cette façon, vous évitez les fuites de mémoire (parce que c'est mauvais) et vous vous assurez de ne pas avoir un fragment statique [expletive] qui ne peut pas accéder aux champs et méthodes de votre activité. C'est la bonne façon de gérer ce problème, de mon point de vue.

5voto

chakri Reddy Points 2920

Si vous le développez dans Android studio, il n'y a pas de problème si vous ne le donnez pas en tant que static. Le projet s'exécutera sans aucune erreur et au moment de la génération de l'apk, vous obtiendrez Error :This fragment inner class should be static [ValidFragment].

C'est une erreur de lint, vous construisez probablement avec gradle, pour désactiver l'abandon sur les erreurs, ajoutez :

lintOptions {
    abortOnError false
}

dans le fichier build.gradle. `

5voto

Deepu Points 588

Si vous voulez accéder aux membres de la classe externe (Activity) et ne voulez toujours pas rendre les membres statiques dans Activity (puisque le fragment devrait être public static), vous pouvez faire la surcharge onActivityCreated

public static class MyFragment extends ListFragment {

    private OuterActivityName activity; // outer Activity

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        activity = (OuterActivityName) getActivity();
        ...
        activity.member // accessing the members of activity
        ...
     }

-2voto

Sanket Parchande Points 546

Ajouter une annotation avant la classe interne

@SuppressLint("validFragment")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X