Fragmenty a back Stack w Androidzie

Wiemy już czym jest stos oraz zadania. Dowiedziałeś się również, jakie mamy tryby uruchomienia. Aby uzupełnić temat musimy jeszcze porozmawiać o fragmentach. Do tej pory wiesz, że przycisk wstecz wywołuje poprzednią aktywność. Jednak transakcje umieszczane na stosie nie muszą być wyłącznie aktywnościami. Mogą nimi być także fragmenty wyświetlane na ekranie urządzenia. Fragmenty a Back Stack w Androidzie to specyficzny temat, a ten wpis pokaże Ci, w jaki sposób umieszczać fragmenty na back stack czyli stosie 🙂

Kilka informacji

Przypomnijmy sobie, w jaki sposób dodajemy fragmenty do aplikacji. Mamy dwie możliwości:

  • dodawanie dynamicznie – w layout aktywności definiujemy <FrameLayout … />. Jest to pojemnik na fragmenty. Zamiast tworzyć nową aktywność, dynamicznie podmieniamy fragment, który jest aktualnie wyświetlany w pojemniku.
  • dodawanie statycznie – w layout aktywności tworzymy fragmenty statycznie za pomocą znacznika <fragment… />. W jednej aktywności możemy wyświetlić tyle fragmentów na ile pozwala nam miejsce. Jeśli potrzebujemy więcej fragmentów, tworzymy kolejne aktywności dla każdego fragmentu.

Który sposób wybrać, statyczny czy dynamiczny? To zależy od konkretnego przypadku. Dynamiczny sposób jest bardziej elastyczny i jego polecam, ale też sprawia pewne trudności. Statyczne fragmenty nie stanowią problemu, ponieważ już wiesz jak obsługiwać stos w aktywnościach. Dlatego dziś zajmiemy się tylko dynamicznymi fragmentami.

Dodanie fragmentu do stosu.

Przyjrzyjmy się teraz, w jaki sposób są dodawane fragmenty do aktywności w podstawowej formie:

// tworzymy transakcje
FragmentTransaction ft = getFragmentManager().beginTransaction();
// dodajemy fragment do transakcji
ft.add(R.id.fragment_frame_layout, new DefaultFragment());
// Lub
// ft.replace(R.id.fragment_frame_layout, DefaultFragment, "Default Fragment");
// dodajemy transakcję do stosu
ft.addToBackStack(null);
// zatwierdzamy transakcję
ft.commitAllowingStateLoss();

Nic trudnego. Prawda? W wielu poradnikach dotyczących fragmentów możesz też spotkać z metodą commit() (ja skorzystałem z commitAllowingStateLoss()) – która zatwierdza transakcje. Jest to jak najbardziej poprawne, ale są pewne różnice między tymi metodami. Nie będę tutaj opisywał tego, ponieważ jest to temat na osobny wpis.

Różnica między add() a replace()

Metoda add() – dodaje fragment do aktywności. Może on mieć opcjonalnie swój widok.

Metoda replace() – zachowuje się podobnie co add(), ale na początku usuwa poprzednie fragmenty, a następnie dodaje fragment. Czyli zastępuje istniejący fragment, który został dodany do kontenera. Jest to w zasadzie to samo, co wywołanie remove() dla wszystkich aktualnie dodanych fragmentów, które zostały dodane z tym samym containerViewId, a następnie dodane.

Jeśli twoje fragmenty mają przezroczyste tło, zobaczysz różnice między tymi metodami i będziesz w stanie współdziałać z wieloma fragmentami w tym samym czasie. Tak się stanie, jeśli użyjesz add() na kontenerze.

W przypadku replace(), usunie wszystkie fragmenty znajdujące się już w pojemniku i doda nowe do tego samego kontenera. Nie myl z usuwaniem fragmentu ze stosu!

Możesz również powrócić do poprzedniego fragmentu w stosie za pomocą metody popBackStack(). W tym celu należy dodać fragment do back stack za pomocą metody addToBackStack(), a następnie zatwierdzić transakcje.

Jeszcze jedna ważna różnica między add() a replace(). Metoda replace() usuwa istniejący fragment i dodaje nowy fragment. Oznacza to, że po naciśnięciu przycisku “Wstecz” fragment, który został zastąpiony, zostanie utworzony ponownie wywołując metodę onCreateView()

Podczas gdy add() zachowuje istniejące fragmenty i dodaje nowy fragment, co oznacza, że ​​istniejący fragment będzie aktywny, a więc po naciśnięciu przycisku wstecz, metoda onCreateView() nie jest wywoływana dla istniejącego fragmentu.

Jeśli chodzi o zdarzenia cyklu życia fragmentu, metody: onPause, onResume, onCreateView, itp będą wywoływane w przypadku, replace(), ale nie będą one wywoływane w przypadku add(). Aby to łatwiej zrozumieć zobacz na przykład.

Metoda add()

Otwieramy Fragment1, zostaną wywołane następujące metody:

Fragment1: onCreateView
Fragment1: onStart
Fragment1: onResume


Idziemy do Fragment2:

Fragment2: onCreateView
Fragment2: onStart
Fragment2: onResume

Pop Fragment2 – wywołanie metody popBackStack()

Fragment2: onStop
Fragment2: onDestroyView
Fragment2: onDestroy

Pop Fragment1:

Fragment1: onStop
Fragment1: onDestroyView
Fragment1: onDestroy

Metoda Replace()

Otwieramy Fragment1, zostaną wywołane następujące metody:

Fragment1: onCreateView
Fragment1: onStart
Fragment1: onResume

Idziemy do Fragment2:

Fragment1: onStop
Fragment1: onDestroyView
Fragment2: onCreateView
Fragment2: onStart
Fragment2: onResume

Pop Fragment2 – wywołanie metody popBackStack()

Fragment2: onStop
Fragment2: onDestroyView
Fragment2: onDestroy
Fragment1: onCreateView
Fragment1: onStart
Fragment1: onResume

Pop Fragment1:

Fragment1: onStop
Fragment1: onDestroyView
Fragment1: onDestroy

Brak fragmentu w back stack

Nie trudno się domyślić, w jaki sposób nie dodawać fragmentu do stosu. Wystarczy nie stosować metody addToBackStack(). Czy to jedyny sposób? Okazuje się, że nie. Jeżeli chciałbyś usunąć wszystkie fragmenty ze stosu możesz użyć tego kodu:

int backStackEntry = getFragmentManager().getBackStackEntryCount();
if (backStackEntry > 0) {
    for (int i = 0; i < backStackEntry; i++) {
        getFragmentManager().popBackStackImmediate();
    }
}

Wcześniej wspomniałem o metodzie popBackStack(). Czym ona się różni od popBackStackImmediate()? PopBackStackImmediate() wykona polecenia natychmiast. Rezultaty są widoczne natychmiast po wywołaniu tej funckji. Ta metoda jest nieco wolniejsza, ponieważ wszystkie akcje “popping” wykonywane są w ramach transakcji. Pamiętaj, że tę metodę musisz wywołać przed dodaniem fragmentu do aktywności, w przeciwnym wypadku dostaniesz błąd. 

Metoda popBackStack() powodują umieszczenie zdarzenia w kolejce zadań do wykonania przez FragmentManagera zmiany będą obowiązywać tylko wtedy, gdy aplikacja wróci do pętli zdarzeń.

Podczas umieszczanie fragmentu w back stack, programiści Androida najczęściej używają metody addToBackStack(null). Dlaczego zawsze przekazujemy wartość null? Okazuje się, że możemy przekazać coś innego, co sprawi, że ​​fragment będzie ponownie użyteczny.

Tag fragmentu możesz użyć do znalezienia określonego fragmentu w FragmentManager przez findFragmentByTag(). Należy pamiętać, że Fragment musi zostać dodany do FragmentManagera. Jeżeli nie dodasz go do back stack, nie będziesz mógł go znaleźć.†

Uzupełnij wiedzę o: Lombok w Androidzie

Uważaj na przycisk wstecz!

Co się stanie, gdy w pierwszej kolejności fragment A utworzył transakcję wyświetlającą fragment B, używając metody getFragmentManager()? Jeśli do wyświetlenia fragmentu B użyjemy metody
getFragmentManager(), to na stosie cofnięć pojawią się dwie transakcje. Problem związany z zastosowaniem dwóch transakcji do wyświetlenia fragmentów polega na tym, że w takim przypadku po kliknięciu przycisku Wstecz mogą się dziać dziwne rzeczy. Kiedy użytkownik kliknie fragment A, a następnie naciśnie przycisk Wstecz, to będzie oczekiwał, że ekran wróci do poprzedniego wyglądu, czyli aktywności. Jednak naciśnięcie przycisku wstecz wycofuje jedynie ostatnią transakcję ze stosu – czyli usunie tylko fragment B. Zatem użytkownik musi nacisnąć dwa razy przycisk wstecz.

Metody getFragmentManager() używaj do tworzenia transakcji na poziomie aktywności, natomiast na poziomie fragmentów korzystaj z getChildFragmentManager(). 

Po zastosowaniu nowej metody na stosie zostaje umieszczona tylko jedna transakcja, która zawiera kolejną transakcję. Kiedy użytkownik wybierze aktywność, a ona wyświetli fragment A wraz z fragmentem B, to będzie jedna transakcja. Po naciśnięciu przycisku Wstecz aplikacja zachowa się prawidłowo.

Weryfikacja.

Podobnie jak przy aktywnościach możesz zweryfikować, w jaki sposób fragmenty są zarządzane w stosie. Wydaj polecenie:

adb.exe shell dumpsys activity net.myenv.myapplication

Dostaniesz następujący wynik:

TASK net.myenv.myapplication id=709 userId=0
  ACTIVITY net.myenv.myapplication/.Fragment.FragmentBackStackActivity 59e9bd7 pid=21083
    Local Activity 292c25e State:
      mResumed=true mStopped=false mFinished=false
      mChangingConfigurations=false
      mCurrentConfig={1.0 310mcc260mnc [en_US] ldltr sw375dp w375dp h595dp 460dpi nrml port finger qwerty/v/v -nav/h appBounds=Rect(0, 0 - 1080, 1782) s.6}
      mLoadersStarted=true
      Active Fragments in 160d7ca:
  [...]
        #1: DefultFragment{c259e3b #1 id=0x7f070047}
          mFragmentId=#7f070047 mContainerId=#7f070047 mTag=null
          mState=5 mIndex=1 mWho=android:fragment:1 mBackStackNesting=0
          mAdded=true mRemoving=false mFromLayout=false mInLayout=false
          mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
          mRetainInstance=false mRetaining=false mUserVisibleHint=true
          mFragmentManager=FragmentManager{160d7ca in HostCallbacks{8381c58}}
          mHost=android.app.Activity$HostCallbacks@8381c58
          mContainer=android.widget.FrameLayout{278d217 V.E...... ........ 0,0-1080,1121 #7f070047 app:id/fragment_frame_layout}
          mView=android.widget.LinearLayout{23ea604 V.E...... ........ 0,0-1080,1121}
          Child FragmentManager{65a17ed in DefultFragment{c259e3b}}:
            FragmentManager misc state:
              mHost=android.app.Activity$HostCallbacks@8381c58
              mContainer=android.app.Fragment$1@5f49622
              mParent=DefultFragment{c259e3b #1 id=0x7f070047}
              mCurState=5 mStateSaved=false mDestroyed=false
        #2: DefultFragment{75e1d9e #2 id=0x7f070047 Defult Fragment}
  [...]
      Added Fragments:
        #0: ReportFragment{8379d3b #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag}
        #1: DefultFragment{c259e3b #1 id=0x7f070047}
        #2: DefultFragment{75e1d9e #2 id=0x7f070047 Defult Fragment}
        #3: FragmentThree{97520f3 #3 id=0x7f070047 Fragment three}
        #4: DefultFragment{99c2c72 #4 id=0x7f070047 Defult Fragment}
      Back Stack:
        #0: BackStackEntry{9974b88 #0}
          mName=null mIndex=0 mCommitted=true
          Operations:
            Op #0: ADD DefultFragment{75e1d9e #2 id=0x7f070047 Defult Fragment}
        #1: BackStackEntry{19ec921 #1}
          mName=null mIndex=1 mCommitted=true
          mTransition=#2002 mTransitionStyle=#0
          Operations:
            Op #0: ADD FragmentThree{97520f3 #3 id=0x7f070047 Fragment three}
        #2: BackStackEntry{269e146 #2}
          mName=null mIndex=2 mCommitted=true
          Operations:
            Op #0: ADD DefultFragment{99c2c72 #4 id=0x7f070047 Defult Fragment}
      Back Stack Indices:
        #0: BackStackEntry{9974b88 #0}
        #1: BackStackEntry{19ec921 #1}
        #2: BackStackEntry{269e146 #2}
      FragmentManager misc state:
        mHost=android.app.Activity$HostCallbacks@8381c58
        mContainer=android.app.Activity$HostCallbacks@8381c58
        mCurState=5 mStateSaved=false mDestroyed=false
  [...]

Od linii 10 masz informacje o fragmencie w jaki sposób został dodany oraz jego parametry. Od linii 28 jest pokazano jakie zostały dodane fragmenty do pojemnika. Nie ma znaczenia, w jaką metodą dodaliśmy te fragmenty. Natomiast od linii 34 jest pokazany stos fragmentów.

Podsumowanie.

Fragmenty w Back Stack to dość specyficzny temat, który na pewno powinieneś znać, jeśli korzystasz z fragmentów. Zwracaj uwagę na to, w jaki sposób dodajesz fragmenty. Zastanów się przez chwilę czy dany fragment ma pojawiać się, gdy użytkownik naciśnie przycisk wstecz. Jeśli nie to wiesz już co robić 🙂

Co dalej?

  • Polub stronę MYENV na Facebooku oraz śledź mnie na Twitterze
  • Zachęcam do komentowania i pisania propozycji tematów, o których chcesz przeczytać
  • Poleć ten wpis za pomocą poniższych przycisków. Będę Ci za to bardzo wdzięczny 🙂
  • Życzę Ci miłego dnia i miłego kodowania 🙂