बैक स्टैक में फ्रैगमेंट की आवृत्ति स्थिति को सही ढंग से कैसे बचाएं?


488

मुझे एसओ पर एक समान प्रश्न के कई उदाहरण मिले हैं लेकिन कोई भी जवाब दुर्भाग्य से मेरी आवश्यकताओं को पूरा नहीं करता है।

मेरे पास चित्र और परिदृश्य के लिए अलग-अलग लेआउट हैं और मैं बैक स्टैक का उपयोग कर रहा हूं, जो दोनों मुझे setRetainState()कॉन्फ़िगरेशन परिवर्तन दिनचर्या का उपयोग करने और ट्रिक्स से बचाता है ।

मैं उपयोगकर्ता को TextViews में कुछ जानकारी दिखाता हूं, जो डिफ़ॉल्ट हैंडलर में सहेजे नहीं जाते हैं। पूरी तरह से गतिविधियों का उपयोग करते हुए मेरे आवेदन को लिखते समय, निम्नलिखित ने अच्छी तरह से काम किया:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

Fragmentएस के साथ , यह केवल बहुत विशिष्ट स्थितियों में काम करता है। विशेष रूप से, जो कुछ टूटता है वह एक टुकड़े को बदल रहा है, इसे पीछे के ढेर में डाल दिया जाता है और फिर स्क्रीन को घुमाते हुए नया टुकड़ा दिखाया जाता है। जो मैंने समझा था, पुराने टुकड़े को onSaveInstanceState()प्रतिस्थापित होने पर कॉल नहीं मिलता है लेकिन किसी तरह से जुड़ा रहता है Activityऔर इस पद्धति को बाद में कहा जाता है जब इसका Viewकोई अस्तित्व नहीं होता है, इसलिए मेरे किसी भी TextViewपरिणाम की तलाश में NullPointerException

इसके अलावा, मैंने पाया कि मेरे लिए संदर्भ रखना s के TextViewsसाथ अच्छा विचार नहीं है Fragment, भले ही वह Activity's' के साथ ठीक हो । उस स्थिति में, onSaveInstanceState()वास्तव में राज्य को बचाता है, लेकिन टुकड़ा फिर से दिखाई देने पर स्क्रीन को दो बार घुमाए जाने पर समस्या फिर से प्रकट होती है, क्योंकि इसके onCreateView()नए उदाहरण में कॉल नहीं मिलता है।

मैं में राज्य को बचाने के बारे में सोचा onDestroyView()कुछ में Bundleप्रकार वर्ग के सदस्य तत्व (यह वास्तव में और अधिक डेटा, न सिर्फ एक है TextViewऔर बचत) कि में onSaveInstanceState()लेकिन कुछ अन्य कमियां हैं। जाहिर है, अगर टुकड़ा है वर्तमान में दिखाया गया है, दो कार्यों बुला के आदेश को पलट तो मैं दो अलग-अलग परिस्थितियों के लिए खाते में आवश्यकता होगी है। एक क्लीनर और सही समाधान होना चाहिए!


1
यहाँ विवरण स्पष्टीकरण के साथ बहुत अच्छा उदाहरण है। emuneee.com/blog/2013/01/07/saving-fragment-states
Hesam

1
मैं emunee.com लिंक को दूसरा स्थान देता हूं। इसने मेरे लिए एक छड़ी यूआई समस्या हल कर दी!
रेवेन्टर रोब

जवाबों:


541

Fragmentआप की आवृत्ति स्थिति को सही ढंग से सहेजने के लिए निम्नलिखित कार्य करना चाहिए:

1. टुकड़े में, ओवरराइड करके onSaveInstanceState()और पुनर्स्थापना करके उदाहरण स्थिति सहेजें onActivityCreated():

class MyFragment extends Fragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's state here
        }
    }
    ...
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's state here
    }

}

2. और महत्वपूर्ण बिंदु , गतिविधि में, आपको टुकड़े की आवृत्ति को बचाने onSaveInstanceState()और अंदर पुनर्स्थापित करना होगा onCreate()

class MyActivity extends Activity {

    private MyFragment 

    public void onCreate(Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's instance
            mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
            ...
        }
        ...
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's instance
        getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
    }

}

उम्मीद है की यह मदद करेगा।


7
यह पूरी तरह से मेरे लिए काम किया! कोई वर्कअराउंड, कोई हैक नहीं, बस इस तरह से समझ में आता है। इसके लिए धन्यवाद, खोज को सफल बनाने के घंटे बनाए। SaveInstanceState () के टुकड़े में अपने मान, फिर खंड में पकड़े हुए गतिविधि में टुकड़े को बचाएं, फिर पुनर्स्थापित करें :)
MattMatt

77
MContent क्या है?
१iz बजे विज़र्ड

14
@wizurd mContent एक फ़्रैगमेंट है, यह गतिविधि में वर्तमान टुकड़े के उदाहरण का संदर्भ है।
थानएचएच

13
क्या आप बता सकते हैं कि यह बैक स्टैक में खंड के उदाहरण को कैसे बचाएगा? यही ओपी ने पूछा।
हिटमैनएडोस 16

51
यह सवाल से संबंधित नहीं है, onSaveInstance को तब नहीं बुलाया जाता है जब टुकड़े को बैकस्टैक में डाल दिया जाता है
तदास वैलिटिस

87

इस तरह से मैं इस समय का उपयोग कर रहा हूं ... यह बहुत जटिल है लेकिन कम से कम यह सभी संभावित स्थितियों को संभालता है। मामले में किसी को दिलचस्पी है।

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

वैकल्पिक रूप से , यह हमेशा डेटा Viewको चर में निष्क्रिय एस में Viewप्रदर्शित करने और केवल उन्हें प्रदर्शित करने के लिए उपयोग करने की संभावना है , दो चीजों को सिंक में रखता है। मैं पिछले भाग को बहुत साफ नहीं मानता, हालाँकि।


68
यह सबसे अच्छा समाधान है जो मैंने अब तक पाया है, लेकिन अभी भी एक (कुछ हद तक विदेशी) समस्या शेष है: यदि आपके पास दो टुकड़े हैं, Aऔर B, जहां Aवर्तमान में बैकस्टैक पर है और Bदिखाई देता है, तो आप A(अदृश्य) की स्थिति खो देते हैं एक) यदि आप प्रदर्शन को दो बार घुमाते हैं । समस्या यह है कि onCreateView()केवल इस परिदृश्य में नहीं बुलाया जाता है onCreate()। इसलिए बाद में, onSaveInstanceState()राज्य को बचाने के लिए कोई विचार नहीं हैं। एक को स्टोर करना होगा और फिर पास में रखे राज्य को बचाना होगा onCreate()
Devconsole

7
@devconsole काश मैं आपको इस टिप्पणी के लिए 5 वोट दे पाता! दो बार घूमने वाली यह चीज मुझे दिनों से मार रही है।
DroidT

शानदार जवाब के लिए धन्यवाद! हालांकि मैंरे पास भी एक सवाल है। इस खंड में मॉडल ऑब्जेक्ट (POJO) को इंस्टेंट करने के लिए सबसे अच्छी जगह कहां है?
रंजीत

7
दूसरों के लिए समय बचाने में मदद करने के लिए, App.VSTUPऔर App.STAVदोनों स्ट्रिंग टैग हैं जो उन वस्तुओं का प्रतिनिधित्व करते हैं जिन्हें वे प्राप्त करने की कोशिश कर रहे हैं। उदाहरण: savedState = savedInstanceState.getBundle(savedGamePlayString);याsavedState.getDouble("averageTime")
टान्नर हॉलमैन

1
यह सुंदरता की चीज है।
इवान

61

नवीनतम समर्थन लाइब्रेरी पर यहां चर्चा किए गए समाधानों में से कोई भी अब आवश्यक नहीं है। Activityजैसा कि आप का उपयोग कर के रूप में आप अपने टुकड़े के साथ खेल सकते हैं FragmentTransaction। बस यह सुनिश्चित करें कि आपके अंशों की पहचान किसी आईडी या टैग से की जा सकती है।

जब तक आप उन्हें हर कॉल पर पुनः बनाने की कोशिश नहीं करेंगे तब तक टुकड़े स्वतः ही बहाल हो जाएंगे onCreate()। इसके बजाय, आपको जांचना चाहिए कि savedInstanceStateक्या अशक्त नहीं है और इस मामले में बनाए गए टुकड़ों के पुराने संदर्भों को ढूंढें।

यहाँ एक उदाहरण है:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

ध्यान दें कि एक खंड के छिपे हुए राज्य को पुनर्स्थापित करते समय वर्तमान में एक बग है । यदि आप अपनी गतिविधि में टुकड़े छिपा रहे हैं, तो आपको इस मामले में मैन्युअल रूप से इस स्थिति को पुनर्स्थापित करने की आवश्यकता होगी।


2
क्या यह फिक्स कुछ है जिसे आपने समर्थन पुस्तकालय का उपयोग करते समय देखा था या आपने इसके बारे में कहीं पढ़ा था? क्या कोई और जानकारी है जो आप इसके बारे में प्रदान कर सकते हैं? धन्यवाद!
21

1
@Piovezan यह डॉक्स से अंतर्निहित अनुमान की तरह हो सकता है। उदाहरण के लिए, startTransaction () डॉक निम्नानुसार पढ़ता है: "ऐसा इसलिए है क्योंकि फ्रेमवर्क राज्य में आपके वर्तमान टुकड़ों को बचाने का ख्याल रखता है (...)"। मैं भी काफी समय से इस अपेक्षित व्यवहार के साथ अपने ऐप्स को कोड कर रहा हूं।
रिकार्डो

1
यदि दृश्य व्यूगर का उपयोग किया जा रहा है तो क्या यह लागू होता है?
डेरेक बीट्टी

1
आम तौर पर हां, जब तक कि आप अपने कार्यान्वयन के डिफ़ॉल्ट व्यवहार को नहीं बदलते FragmentPagerAdapterया FragmentStatePagerAdapter। यदि आप FragmentStatePagerAdapterउदाहरण के लिए कोड को देखते हैं, तो आप देखेंगे कि एडेप्टर बनाते समय पैरामीटर आपके द्वारा दिए गए restoreState()अंशों को पुनर्स्थापित करता है FragmentManager
रिकार्डो

4
मुझे लगता है कि यह योगदान मूल प्रश्न का सबसे अच्छा उत्तर है। यह भी एक है कि - मेरी राय में - एंड्रॉइड प्लेटफॉर्म कैसे काम करता है, इसके साथ सबसे अच्छा गठबंधन किया गया है। मैं इस उत्तर को "स्वीकृत" के रूप में चिह्नित करने की सलाह दूंगा ताकि भविष्य के पाठकों को बेहतर मदद मिल सके।
dbm

17

मैं बस उस समाधान को देना चाहता हूं जो मैं इस पोस्ट में प्रस्तुत किए गए सभी मामलों को संभालता हूं जो कि मैं वासेक और डेकोन्कोलॉजी से प्राप्त करता हूं। यह समाधान उस विशेष मामले को भी संभालता है जब फोन को एक से अधिक बार घुमाया जाता है जबकि टुकड़े दिखाई नहीं देते हैं।

यहाँ मैं बाद में उपयोग के लिए बंडल स्टोर कर रहा था क्योंकि onCreate और onSaveInstanceState केवल कॉल हैं जो तब दिखाई देते हैं जब टुकड़े दिखाई नहीं देते हैं

MyObject myObject;
private Bundle savedState = null;
private boolean createdStateInDestroyView;
private static final String SAVED_BUNDLE_TAG = "saved_bundle";

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

चूँकि नष्ट करने के दृश्य को विशेष रोटेशन की स्थिति में नहीं कहा जाता है तो हम निश्चित हो सकते हैं कि यदि यह राज्य बनाता है तो हमें इसका उपयोग करना चाहिए।

@Override
public void onDestroyView() {
    super.onDestroyView();
    savedState = saveState();
    createdStateInDestroyView = true;
    myObject = null;
}

यह हिस्सा समान होगा।

private Bundle saveState() { 
    Bundle state = new Bundle();
    state.putSerializable(SAVED_BUNDLE_TAG, myObject);
    return state;
}

अब यहाँ मुश्किल हिस्सा है। मेरे onActivityCreated पद्धति में मैं "myObject" चर को तुरंत समाप्त कर देता हूं, लेकिन रोटेशन होता है onActivity और onCreateView को कॉल नहीं किया जाता है। वहाँ, myObject इस स्थिति में शून्य होगा जब ओरिएंटेशन एक से अधिक बार घूमता है। मैं इसे उसी बंडल का पुन: उपयोग करके प्राप्त करता हूं जिसे ऑन-क्रिएट में सहेजा गया था और बाहर जाने वाले बंडल के रूप में।

    @Override
public void onSaveInstanceState(Bundle outState) {

    if (myObject == null) {
        outState.putBundle(SAVED_BUNDLE_TAG, savedState);
    } else {
        outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState());
    }
    createdStateInDestroyView = false;
    super.onSaveInstanceState(outState);
}

अब आप जहां भी राज्य को पुनर्स्थापित करना चाहते हैं, बस सेव किए गए बंडल का उपयोग करें

  @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if(savedState != null) {
        myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
    }
    ...
}

क्या आप मुझे बता सकते हैं ... यहाँ "MyObject" क्या है?
कावी

2
कुछ भी आप यह चाहते हैं। यह सिर्फ एक उदाहरण है जो किसी चीज़ का प्रतिनिधित्व करता है जिसे बंडल में सहेजा जाएगा।
DroidT

3

DroidT के लिए धन्यवाद , मैंने इसे बनाया:

मुझे एहसास है कि अगर Fragment onCreateView () को निष्पादित नहीं करता है, तो इसका दृश्य त्वरित नहीं है। इसलिए, यदि बैक स्टैक पर टुकड़े ने अपने विचार नहीं बनाए, तो मैं अंतिम संग्रहीत स्थिति को सहेजता हूं, अन्यथा मैं अपने डेटा के साथ अपना बंडल बनाता हूं जिसे मैं सहेजना / पुनर्स्थापित करना चाहता हूं।

1) इस वर्ग का विस्तार करें:

import android.os.Bundle;
import android.support.v4.app.Fragment;

public abstract class StatefulFragment extends Fragment {

    private Bundle savedState;
    private boolean saved;
    private static final String _FRAGMENT_STATE = "FRAGMENT_STATE";

    @Override
    public void onSaveInstanceState(Bundle state) {
        if (getView() == null) {
            state.putBundle(_FRAGMENT_STATE, savedState);
        } else {
            Bundle bundle = saved ? savedState : getStateToSave();

            state.putBundle(_FRAGMENT_STATE, bundle);
        }

        saved = false;

        super.onSaveInstanceState(state);
    }

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);

        if (state != null) {
            savedState = state.getBundle(_FRAGMENT_STATE);
        }
    }

    @Override
    public void onDestroyView() {
        savedState = getStateToSave();
        saved = true;

        super.onDestroyView();
    }

    protected Bundle getSavedState() {
        return savedState;
    }

    protected abstract boolean hasSavedState();

    protected abstract Bundle getStateToSave();

}

2) आपके टुकड़े में, आपके पास यह होना चाहिए:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3) उदाहरण के लिए, आप onActivity में hasSavedState कॉल कर सकते हैं:

@Override
public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);

    if (hasSavedState()) {
        return;
    }

    //your code here
}

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.