web-dev-qa-db-fra.com

Enregistrer/restaurer les fragments de l'état Android

J'essaie de créer une application qui utilise la bibliothèque ViewPager de Jack Wharton ( ici ) en utilisant uniquement des images pour chaque page (quelque chose comme l'écran principal de Prixing ( ici )) . Tout fonctionne correctement, à l'exception de le saveInstance dans le fragment. 

Dans l'exemple de Jack Wharton, il stocke le texte dans une variable String, nommée mContent, et le restaure dans onCreate, mais dans mon cas, que dois-je faire? Enregistrer/restaurer une image bitmap?! Toute réponse objective serait très appréciée!

Je suis assez nouveau dans ce domaine en utilisant des applications, car chaque exemple que je regarde ne présente que les bases, et il devient difficile de résoudre des problèmes plus complexes.

PS. S'il est utile de savoir, j'utilise CirclePageIndicator.

Code de fragment actuel ici: 

    public final class SpecialOfferFragment extends Fragment {

    private int imageResourceId;

    public static SpecialOfferFragment newInstance(int i) {

        //probably I'll use a bitmap(downloaded) as parameter instead of using static images
        SpecialOfferFragment fragment = new SpecialOfferFragment();

        fragment.imageResourceId = i;

        return fragment;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // if ((savedInstanceState != null) { }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        ImageView image = new ImageView(getActivity());
        image.setImageResource(imageResourceId);
        image.setScaleType(ScaleType.FIT_XY);

        LinearLayout layout = new LinearLayout(getActivity());
        layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));

        layout.setGravity(Gravity.CENTER);
        layout.addView(image);

        return layout;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //smth to save here..
    }
}

Dans cet état actuel de l'application, j'obtiens l'exception suivante: 

04-12 07:28:17.760: E/AndroidRuntime(31903): FATAL EXCEPTION: main 
04-12 07:28:17.760: E/AndroidRuntime(31903): Java.lang.NullPointerException
04-12 07:28:17.760: E/AndroidRuntime(31903): at Android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.Java:1576)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.Java:1617)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.Java:481)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.Activity.performSaveInstanceState(Activity.Java:1113)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.Java:1188)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.ActivityThread.performStopActivityInner(ActivityThread.Java:2804)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.ActivityThread.handleStopActivity(ActivityThread.Java:2862)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.ActivityThread.access$900(ActivityThread.Java:127)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.ActivityThread$H.handleMessage(ActivityThread.Java:1175)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.os.Handler.dispatchMessage(Handler.Java:99)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.os.Looper.loop(Looper.Java:137)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Android.app.ActivityThread.main(ActivityThread.Java:4511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Java.lang.reflect.Method.invokeNative(Native Method)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at Java.lang.reflect.Method.invoke(Method.Java:511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:980)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:747)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at dalvik.system.NativeStart.main(Native Method)

Cela se produit chaque fois que je mets en pause/arrête l’activité contenant ce fragment, comme si j’appuyais sur le bouton Accueil.

**

EDIT avec le code:

**

 public class MainMenu extends FragmentActivity {

    //private List<CategoriesHolder> categoriesList = new ArrayList<CategoriesHolder>();
    //private CategoriesAdapter categoriesAdapter = null;
    //private GridView gv_mainmenu_categories;

    SpecialOfferFragmentAdapter mAdapter;
    ViewPager mPager;
    PageIndicator mIndicator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_mainmenu);

        linkUI();
        setAction();

        // just for testing
        String[] imagesUrls = null;

        mAdapter = new SpecialOfferFragmentAdapter(getSupportFragmentManager(),
                this, imagesUrls);

        //categoriesAdapter = new CategoriesAdapter(this, categoriesList);

        // used for Categories GridView
        //gv_mainmenu_categories.setAdapter(categoriesAdapter);

        // used for ViewPager
        mPager.setAdapter(mAdapter);
        mIndicator.setViewPager(mPager);

    }

    private void linkUI() {
        mPager = (ViewPager) findViewById(R.id.vp_mainmenu_special_offers);
        mIndicator = (CirclePageIndicator) findViewById(R.id.indicator);

        //gv_mainmenu_categories = (GridView) findViewById(R.id.gv_mainmenu_categories);

    }

    private void setAction() {

    }
} 

    public class SpecialOfferFragmentAdapter extends FragmentPagerAdapter implements
        IconPagerAdapter {

    private int[] mCarouselImages = new int[] { R.drawable.pic1,
            R.drawable.pic2, R.drawable.pic3, R.drawable.pic4, R.drawable.pic5

    };

    private String[] imagesUrls;
    private Context context;

    public static final int[] ICONS = new int[] {
            R.drawable.perm_group_calendar, R.drawable.perm_group_camera,
            R.drawable.perm_group_device_alarms, R.drawable.perm_group_location };

    private int mCount = mCarouselImages.length;

    public SpecialOfferFragmentAdapter(FragmentManager fm, Context context,
            String[] imagesUrls) {
        super(fm);
        this.context = context;
        this.imagesUrls = imagesUrls;
    }

    @Override
    public Fragment getItem(int position) {

        return SpecialOfferFragment.newInstance(mCarouselImages[position]);
    }

    @Override
    public int getCount() {
        return mCount;
    }

    @Override
    public int getIconResId(int index) {
        return ICONS[index % ICONS.length];
    }

    public void setCount(int count) {
        if (count > 0 && count <= 10) {
            mCount = count;
            notifyDataSetChanged();
        }
    }
}
13
DoruAdryan

Pour obtenir ce que vous voulez, vous devez utiliser une valeur entière pour stocker la position actuelle dans votre ViewPager, puis utiliser cette valeur pour définir la bonne position sur votre ViewPager

Par exemple, faites quelque chose comme ceci dans votre FragmentActivity:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  savedInstanceState.putInt("mMyCurrentPosition", mPager.getPosition());
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  mMyCurrentPosition = savedInstanceState.getInt("mMyCurrentPosition");
  // where mMyCurrentPosition should be a public value in your activity.
}

ce qui sauvegardera la dernière position de votre ViewPager. Et après cela dans votre onResume() par exemple, vous pouvez vérifier 

if(mMyCurrentPosition != 0){
    mPager.setCurrentItem(mMyCurrentPosition);
}

Je pense que cela devrait fonctionner, ce n'est pas une bonne pratique de sauvegarder des images/bitmaps en lot (et je ne pense pas que vous puissiez le faire).

10
hardartcore

cela a fonctionné pour moi facilement et simplement dans votre activité principale

 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        onCreate(savedInstanceState);
    }

et à l'intérieur de votre manifeste

 <activity
            Android:configChanges="orientation|screenSize"/>
2
Vivek Pratap Singh

Ok, en premier lieu, je voudrais effacer quelque chose qui pourrait résoudre votre confusion sur savedinstancestate . Savedinstancestate ne peut gérer que les primitives (int, double, float, byte ..) et l’objet String. Cela devrait suffire, mais dans certains cas, vous souhaitez également enregistrer des instances d'autres objets. Pour cela, vous avez besoin d’un autre modèle, par exemple un modèle d’usine contenant un tableau de tous vos bitmaps . Comme par exemple, créez une classe ImageFactory contenant un tableau statique ArrayList de bitmaps . Faites pivoter votre appareil. Les méthodes oncreateview et onactivitycreated sont à nouveau appelées. C’est le moment de vérifier si vous souhaitez récupérer des informations de votre usine ou non. par exemple 

if (savedInstanceState! = null) {// nous avons un ordre de sauvegarde sauvegardé donc il y a probablement des images enregistrées ImageFactory.getBMPs (); }

j'espère que vous pouvez faire quelque chose avec cette information. bonne chance

2
JanCor

J'ai créé ma propre méthode pour enregistrer les données de fragment alors que l'onglet est modifié à l'aide de SharedPreference:

Méthode comme:

  public void saveData() {

    Log.d("msg", "Save Instance");
    SharedPreferences.Editor outState = getActivity().getSharedPreferences("order", Context.MODE_APPEND).edit();

    orderDate = orderDateEditText.getText().toString();
    orderBy = orderByEditText.getText().toString();
    orderMobileNo = orderMobileNoEditText.getText().toString();
    orderTransport = orderTransportEditText.getText().toString();
    orderInvoicePer = orderInvoicePerEditText.getText().toString(); 
    orderCharge = orderChargeEditText.getText().toString();
    orderGoodsDispatch = orderGoodsDispatchEditText.getText().toString();
    orderOthers = orderOthersEditText.getText().toString();
    orderIsDirect = orderIsDirectCheckBox.isChecked() ? 1 : 0;

    outState.putBoolean("saved", true);

    outState.putString("orderDate", orderDate);
    outState.putString("orderBy", orderBy);
    outState.putString("orderMobileNo", orderMobileNo);
    outState.putString("orderTransport", orderTransport);
    outState.putString("orderInvoicePer", orderInvoicePer);
    outState.putString("orderCharge", orderCharge);
    outState.putString("orderGoodsDispatch", orderGoodsDispatch);
    outState.putString("orderOthers", orderOthers);
    outState.putInt("orderIsDirect", orderIsDirect);

    outState.commit();
}

Appelez cette méthode quand 

    @Override
    public void onStop() {
    // TODO Auto-generated method stub
        super.onStop();

        saveData();
}

Remplacement de données sauvegardées avec EditText :

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);

    /***** PUT DATA IN EDITTEXT is ITS AVAILABLE IN BUNDLE *****/
    SharedPreferences orderData = getActivity().getSharedPreferences("order", Context.MODE_APPEND);
    Log.d("msg", ""+orderData.getBoolean("saved", false));

    if(orderData.getBoolean("saved", false))
    {
        orderDate = orderData.getString("orderDate", "");
        orderBy = orderData.getString("orderBy", "");
        orderMobileNo = orderData.getString("orderMobileNo", "");
        orderTransport = orderData.getString("orderTransport", "");
        orderInvoicePer = orderData.getString("orderInvoicePer", ""); 
        orderCharge = orderData.getString("orderCharge", "");
        orderGoodsDispatch = orderData.getString("orderGoodsDispatch", "");
        orderOthers = orderData.getString("orderOthers", "");
        orderIsDirect = orderData.getInt("orderIsDirect", 0);

        orderDateEditText.setText(orderDate);
        orderByEditText.setText(orderBy);
        orderMobileNoEditText.setText(orderMobileNo);
        orderTransportEditText.setText(orderTransport);
        orderInvoicePerEditText.setText(orderInvoicePer);
        orderChargeEditText.setText(orderCharge);
        orderGoodsDispatchEditText.setText(orderGoodsDispatch);
        orderOthersEditText.setText(orderOthers);
        if(orderIsDirect == 0)
            orderIsDirectCheckBox.setChecked(false);
        else
            orderIsDirectCheckBox.setChecked(true);
    }
}

Puisse ce code vous être utile ...

Merci...

1
Pratik Butani

Je ne suis pas tout à fait sûr si cette question vous dérange toujours, car cela fait plusieurs mois. Mais je voudrais partager comment j'ai traité avec cela . Voici le code source:

int FLAG = 0;
private View rootView;
private LinearLayout parentView;

/**
 * The fragment argument representing the section number for this fragment.
 */
private static final String ARG_SECTION_NUMBER = "section_number";

/**
 * Returns a new instance of this fragment for the given section number.
 */
public static Fragment2 newInstance(Bundle bundle) {
    Fragment2 fragment = new Fragment2();
    Bundle args = bundle;
    fragment.setArguments(args);
    return fragment;
}

public Fragment2() {

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    Log.e("onCreateView","onCreateView");
    if(FLAG!=12321){
        rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
        changeFLAG(12321);
    }       
    parentView=new LinearLayout(getActivity());
    parentView.addView(rootView);

    return parentView;
}

/* (non-Javadoc)
 * @see Android.support.v4.app.Fragment#onDestroy()
 */
@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    Log.e("onDestroy","onDestroy");
}

/* (non-Javadoc)
 * @see Android.support.v4.app.Fragment#onStart()
 */
@Override
public void onStart() {
    // TODO Auto-generated method stub
    super.onStart();
    Log.e("onstart","onstart");
}

/* (non-Javadoc)
 * @see Android.support.v4.app.Fragment#onStop()
 */
@Override
public void onStop() {
    // TODO Auto-generated method stub
    super.onStop();
    if(false){
        Bundle savedInstance=getArguments();
        LinearLayout viewParent;

        viewParent= (LinearLayout) rootView.getParent();
        viewParent.removeView(rootView);

    }
    parentView.removeView(rootView);

    Log.e("onStop","onstop");
}
@Override
public void onPause() {
    super.onPause();
    Log.e("onpause","onpause");
}

@Override
public void onResume() {
    super.onResume();
    Log.e("onResume","onResume");
}

Et voici l'activité principale:

/**
 * Fragment managing the behaviors, interactions and presentation of the
 * navigation drawer.
 */
private NavigationDrawerFragment mNavigationDrawerFragment;

/**
 * Used to store the last screen title. For use in
 * {@link #restoreActionBar()}.
 */

public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    setContentView(R.layout.activity_main);

    mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
            .findFragmentById(R.id.navigation_drawer);
    mTitle = getTitle();

    // Set up the drawer.
    mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
            (DrawerLayout) findViewById(R.id.drawer_layout));
}

@Override
public void onNavigationDrawerItemSelected(int position) {
    // update the main content by replacing fragments
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
    switch(position){
    case 0:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
        break;
    case 1:

        Bundle bundle=new Bundle();
        bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);

        if(!fragment2InstanceExists){
            fragment2=Fragment2.newInstance(bundle);
            fragment2InstanceExists=true;
        }
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, fragment2).commit();

        break;
    case 2:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
        break;
    default: 
        break;
    }
}

Le parentView est le point clé. Normalement, quand onCreateView, nous utilisons simplement return rootView. Mais maintenant, j'ajoute rootView à parentView, puis je retourne parentView. Pour empêcher "L'enfant spécifié a déjà un parent. Vous devez appeler removeView () sur l'erreur ...", nous devons appeler "parentView.removeView (rootView)" ou la méthode fournie est inutile. Je voudrais aussi partager comment je l’ai trouvé. Tout d'abord, j'ai mis en place un booléen pour indiquer si l'instance existe. Lorsque l'instance existe, le rootView ne sera pas gonflé à nouveau. Mais alors, logcat a donné à l'enfant déjà un truc de parent, alors j'ai décidé d'utiliser un autre parent comme vue parent intermédiaire. Ça fonctionne comme ça. J'espère que cela vous sera utile. 

1
butch

Laissez-moi écrire ma façon de gérer ce problème. Je garde une variable d'objet fragment dans l'activité, pour une interaction ultérieure. Dans la méthode onCreate (), je vérifie s’il existe un savedInstanceState (provoqué par la rotation de l’écran ou autre chose).

public class MainActivity extends AppCompatActivity {
PlaceAddFragment mFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if(savedInstanceState == null) {
        mFragment = new PlaceAddFragment();
        mFragment.setProfileImages(incomingPics);

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.form_fragment_holder, mFragment, PlaceAddFragment.TAG)
                .commit();
    }else{
        mFragment = (PlaceAddFragment) getSupportFragmentManager().findFragmentByTag(PlaceAddFragment.TAG);
    }
}

Dans ma classe fragment, je substitue la méthode onSaveInstanceState et j'ajoute simplement setRetainInstance (true), comme indiqué ci-dessous:

public class PlaceAddFragment extends Fragment {
public static final String TAG = ".PlaceAddFragment";
private ProfileImageAdapter mAdapter;
private List<ProfileImage> mProfileImages;
private Context mContext;

public PlaceAddFragment() {
    // Required empty public constructor
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    mContext = getContext();
    View view = inflater.inflate(R.layout.fragment_place_add, container, false);

    RecyclerView mRecyclerView = (RecyclerView) view.findViewById(R.id.hw_pictures_holder);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
    mAdapter = new ProfileImageAdapter(mContext, mProfileImages);
    mRecyclerView.setAdapter(mAdapter);

    return view;
}

@Override
public void onSaveInstanceState(final Bundle outState) {
    super.onSaveInstanceState(outState);
    setRetainInstance(true);
}

@Override
public void onDestroy() {
    super.onDestroy();
    mAdapter = null;
    mProfileImages = null;
}

public void setProfileImages(List<ProfileImage> incomingPics) {
    this.mProfileImages = incomingPics;
}

public List<ProfileImage> getProfileImages() {
    return this.mProfileImages;
}
}

Si vous voulez que ce code fonctionne, vous devez implémenter ProfileImageAdapter mAdapter, objet ProfileImage et toutes les dispositions XML pour ceux-ci.

0
MeLine