Fragments onResume from back stack

94

Estou usando o pacote de compatibilidade para usar Fragments com Android 2.2. Ao usar fragmentos e adicionar transições entre eles na pilha de backstack, gostaria de obter o mesmo comportamento de onResume de uma atividade, ou seja, sempre que um fragmento é trazido para "primeiro plano" (visível para o usuário) após sair do backstack, gostaria que algum tipo de retorno de chamada fosse ativado dentro do fragmento (para realizar certas mudanças em um recurso de IU compartilhado, por exemplo).

Percebi que não há retorno de chamada embutido na estrutura do fragmento. existe uma boa prática para conseguir isso?

Oriharel
fonte
13
Ótima pergunta. Estou tentando alterar o título da barra de ações dependendo de qual fragmento está visível como um caso de uso para este cenário e parece-me um retorno de chamada ausente na API.
Manfred Moser
3
Sua atividade pode definir o título desde que saiba quais fragmentos estão sendo exibidos a qualquer momento. Também suponho que você possa fazer algo em onCreateViewque será chamado para o fragmento após um estalo.
PJL
1
@PJL +1 De acordo com a referência essa é a maneira que deve ser feito. No entanto, eu prefiro a forma de ouvinte
momo

Respostas:

115

Por falta de uma solução melhor, coloquei isso funcionando para mim: suponha que eu tenha 1 atividade (MyActivity) e alguns fragmentos que se substituem (apenas um é visível por vez).

Em MyActivity, adicione este ouvinte:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Como você pode ver, estou usando o pacote de compatibilidade).

Implementação getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()será chamado depois que "Voltar" for pressionado. algumas advertências embora:

  1. Ele assume que você adicionou todas as transações à pilha de retorno (usando FragmentTransaction.addToBackStack())
  2. Ele será ativado a cada mudança de pilha (você pode armazenar outras coisas na pilha posterior, como animação), portanto, você pode obter várias chamadas para a mesma instância de fragmento.
Oriharel
fonte
3
Você deve aceitar sua própria resposta como correta, se funcionou para você.
powerj1984
7
Como isso funciona em termos do ID do fragmento que você está consultando? Como é diferente para cada fragmento e o correto é encontrado na pilha posterior?
Manfred Moser
2
@Warpzit esse método não é chamado, e os documentos do Android admitem discretamente que ele nunca será chamado (só é chamado quando a atividade é retomada - o que é nunca, se a atividade já estiver ativa)
Adam
2
Ridículo que temos que fazer isso! Mas que bom que alguém conseguiu encontrar esse "recurso"
stevebot
3
onResume () NÃO é chamado
Marty Miller
33

Mudei um pouco a solução sugerida. Funciona melhor para mim assim:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
Brotoo25
fonte
1
explique a diferença e por que usar isso.
Resfriador matemático de
2
A diferença entre a minha solução e a anterior é que em cada mudança de fragmento como adicionar, substituir ou retroceder na pilha de fragmentos, o método "onResume" do fragmento superior será chamado. Não há nenhum código de fragmento codificado neste método. Basicamente, ele chama onResume () no fragmento que você está vendo em cada alteração, não importa se já foi carregado na memória ou não.
Brotoo25
9
Não há registro para um método chamado getFragments()em SupportFragmentManager ... -1: - /
Protostome
1
OnResume () é chamado. Mas estou obtendo uma tela em branco ... Há outra maneira de resolver esse problema?
kavie
Acho que isso leva a uma exceção ArrayOutOfBounds se backStackEntryCount for 0. Estou enganado? Tentei editar a postagem, mas ela foi rejeitada.
Mike T
6

Depois de um, popStackBack()você pode usar o seguinte retorno de chamada: onHiddenChanged(boolean hidden)dentro do seu fragmento

user2230304
fonte
2
Isso parece não funcionar com fragmentos de compatibilidade de aplicativos.
Lo-Tan
Foi isso que usei. Obrigado!
Albert Vila Calvo
A solução mais simples! Basta adicionar uma verificação se o fragmento está visível e voila, você pode definir o título do appbar.
EricH206
4

A seção a seguir em Desenvolvedores Android descreve um mecanismo de comunicação Criando callbacks de eventos para a atividade . Para citar uma linha dele:

Uma boa maneira de fazer isso é definir uma interface de retorno de chamada dentro do fragmento e exigir que a atividade do host a implemente. Quando a atividade recebe um retorno de chamada por meio da interface, ela pode compartilhar as informações com outros fragmentos no layout, conforme necessário.

Editar: o fragmento possui um onStart(...)que é invocado quando o fragmento está visível para o usuário. Da mesma forma, onResume(...)quando visível e em execução ativa. Eles estão vinculados às suas contrapartes de atividade. Resumindo: useonResume()

PJL
fonte
1
Estou apenas procurando um método na classe Fragment que é ativado sempre que é mostrado ao usuário. seja por causa de FragmentTransaction.add () / replace () ou FragmentManager.popBackStack ().
oriharel
@oriharel Veja a resposta atualizada. Quando a atividade é carregada, você receberá várias chamadas, onAttach, onCreate, onActivityCreated, onStart, onResume. Se você chamou `setRetainInstance (true) 'então você não obterá um onCreate quando o fragmento estiver sendo mostrado novamente ao clicar novamente.
PJL
15
onStart () nunca é chamado ao clicar em "Voltar" entre fragmentos na mesma atividade. Tentei a maioria dos callbacks na documentação e nenhum deles é chamado nesse cenário. veja minha resposta para uma solução alternativa.
oriharel
hmm com compat lib v4 Estou vendo um onResume para meu fragmento. Eu gosto da resposta de @oriharel, pois ela distingue entre se tornar visível por meio de um popBackStack em vez de apenas ser trazido para o primeiro plano.
PJL
3

Na minha atividade onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Use este método para capturar Fragment específico e chamar onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
Quan Nguyen
fonte
2

Um pouco melhorado e embrulhado em uma solução de gerenciamento.

Coisas a ter em mente. FragmentManager não é um singleton, ele gerencia apenas Fragments dentro de Activity, então em cada atividade será novo. Além disso, esta solução até agora não leva o ViewPager em consideração que chama o método setUserVisibleHint () ajudando a controlar a visibilidade dos Fragments.

Sinta-se à vontade para usar as classes a seguir ao lidar com esse problema (usa injeção Dagger2). Chamada em atividade:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
AAverin
fonte
1

Se um fragmento for colocado na pilha de retorno, o Android simplesmente destrói sua visualização. A própria instância do fragmento não é eliminada. Uma maneira simples de começar é ouvir o evento onViewCreated e colocar a lógica "onResume ()" lá.

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
Franjo
fonte
0

Esta é a resposta correta que você pode chamar onResume (), desde que o fragmento esteja anexado à atividade. Alternativamente, você pode usar onAttach e onDetach

iamlukeyb
fonte
1
Você pode postar exemplo, por favor?
kavie
0

onResume () para o fragmento funciona bem ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
Roshan Poudyal
fonte
0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

e a extensão final do seu Frament from RootFragment para ver o efeito

Luu Quoc Thang
fonte
0

Minha solução alternativa é obter o título atual da barra de ações no Fragment antes de defini-lo com o novo título. Dessa forma, uma vez que o Fragment é exibido, posso voltar a esse título.

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
Doces Mahendran
fonte
0

Usei enum FragmentTagspara definir todas as minhas classes de fragmento.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

passar FragmentTags.TAG_FOR_FRAGMENT_A.name()como tag de fragmento.

e agora em

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
Todos
fonte