Existe um método que funciona como iniciar fragmento para resultado?

91

Atualmente, tenho um fragmento em uma sobreposição. Isso é para entrar no serviço. No aplicativo de telefone, cada uma das etapas que desejo mostrar na sobreposição são suas próprias telas e atividades. Existem 3 partes do processo de login e cada uma tinha sua própria atividade que foi chamada com startActivityForResult ().

Agora quero fazer a mesma coisa usando fragmentos e uma sobreposição. A sobreposição mostrará um fragmento correspondente a cada atividade. O problema é que esses fragmentos estão hospedados em uma atividade na API Honeycomb. Posso fazer com que o primeiro fragmento funcione, mas depois preciso startActivityForResult (), o que não é possível. Existe algo na linha de startFragmentForResult () onde posso iniciar um novo fragmento e, quando terminar, ele retornará um resultado para o fragmento anterior?

CACuzcatlan
fonte

Respostas:

57

Todos os fragmentos vivem dentro de atividades. Começar um Fragment para um resultado não faz muito sentido, porque a Activity que o abriga sempre tem acesso a ele e vice-versa. Se o Fragment precisar passar um resultado, ele pode acessar sua Activity e definir seu resultado e finalizá-lo. No caso de trocar fragmentos em uma única atividade, bem, a atividade ainda é acessível por ambos os fragmentos, e toda a sua passagem de mensagem pode simplesmente passar pela atividade.

Lembre-se de que você sempre tem comunicação entre um Fragment e sua Activity. Começar e terminar com um resultado é o mecanismo de comunicação entre as Atividades - As Atividades podem então delegar qualquer informação necessária aos seus Fragmentos.

LeffelMania
fonte
11
Além disso, ao carregar um fragmento de outro, você pode definir o fragmento de destino e retornar ao onActivityResultmétodo do fragmento pai, se desejar.
PJL
4
Sua resposta está incompleta, fragmentos estão usando um ciclo de vida como atividades. Portanto, não podemos passar argumentos por meio de métodos, mas devemos usar bundles para passar valores. Mesmo se o fragmento definir um valor em algum lugar, precisamos saber quando ele terminou. Os fragmentos anteriores devem obter o valor ao iniciar / reiniciar? Esta é uma ideia. Mas não há uma maneira adequada de armazenar o valor, o fragmento pode ser chamado por vários outros fragmentos / atividades.
Loenix
59

Se desejar, existem alguns métodos de comunicação entre fragmentos,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

Você pode ligar de volta usando estes.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
Nagoya0
fonte
Eu não confirmei, mas talvez funcione. portanto, você deve tomar cuidado com vazamentos de memória causados ​​por referência circular por abuso de setTargetFragment().
Nagoya0,
3
Melhor solução até agora! Funciona muito bem ao ter uma atividade e uma pilha de fragmentos, onde tentar encontrar o fragmento certo para notificar seria um pesadelo. Obrigado
Damien Praca
1
@ userSeven7s isso não funcionará para a caixa de substituição , mas deve funcionar para a caixa de ocultação
Muhammad Babar,
1
@ nagoya0 funciona melhor se usarmos WeakReference para o fragmento de destino
quangson91
11

Podemos simplesmente compartilhar o mesmo ViewModel entre fragmentos

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}
Levon Petrosyan
fonte
3
Olá, Levon, acho que o código acima não compartilhará a mesma instância do modelo de exibição. Você está passando este (fragmento) para ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Isso criará duas instâncias separadas para fragmentos. Você precisa passar a atividade ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Boa captura, vou consertar agora.
Levon Petrosyan
4

Meus 2 centavos.

Eu troco os fragmentos trocando um fragmento antigo por um novo usando ocultar e mostrar / adicionar (existente / novo). Portanto, esta resposta é para desenvolvedores que usam fragmentos como eu.

Então eu uso o onHiddenChangedmétodo para saber se o fragmento antigo foi trocado pelo novo. Veja o código abaixo.

Antes de deixar o novo fragmento, defino um resultado em um parâmetro global a ser consultado pelo fragmento antigo. Esta é uma solução muito ingênua.

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}
AlikElzin-kilaka
fonte
O que é getAndReset()método?
EpicPandaForce de
Não é também onResume()chamado no primeiro fragmento quando o segundo é descartado?
PJ_Finnegan
4

Recentemente, o Google acabou de adicionar uma nova capacidade FragmentManagerque tornou oFragmentManager torna capaz de atuar como um armazenamento central para resultados de fragmentos. Podemos passar os dados para frente e para trás facilmente entre os fragmentos.

Fragmento inicial.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Um fragmento que queremos o resultado de volta.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

O snippet é retirado dos documentos oficiais do Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

Com os dados desta resposta escrita, esse recurso ainda está no alphaestado. Você pode experimentar usando esta dependência.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
fonte
2

Em seu fragmento, você pode chamar getActivity (). Isso lhe dará acesso à atividade que criou o fragmento. A partir daí, você pode chamar seu método de personalização para definir os valores ou para passar os valores.

Summved Jain
fonte
1

Existe uma biblioteca Android - FlowR que permite iniciar fragmentos para resultados.

Iniciando um fragmento para resultado.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Tratamento de resultados no fragmento de chamada.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Definindo o resultado no Fragment.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));
Ragunath Jawahar
fonte
1

Uma solução usando interfaces (e Kotlin). A ideia central é definir uma interface de retorno de chamada, implementá-la em sua atividade e, em seguida, chamá-la de seu fragmento.

Primeiro, crie uma interface ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Em seguida, chame de seu filho (neste caso, seu fragmento):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Finalmente, implemente isso em seu pai para receber o retorno de chamada (neste caso, sua atividade):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }
Jake Lee
fonte
0

A maneira mais fácil de passar os dados de volta é setArgument (). Por exemplo, você tem fragment1 que chama fragment2 que chama fragment3, fragment1 -> framgnet2 -> fargement3

No fragmento 1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

No fragmento 2 chamamos de fragmento 3, como de costume

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

Quando terminamos nossa tarefa no fragmento 3 agora chamamos assim:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Agora, no fragmento 2, podemos facilmente chamar argumentos

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
fonte
Tenha cuidado com este, pode funcionar em alguns casos, mas se o seu fragmento tivesse argumentos originais ('fragmento 2' neste exemplo), isso substituiria os argumentos originais usados ​​para criar o fragmento, o que poderia levar a um estado inconsistente se o fragmento é destruído e recriado.
Juan
0

Outra coisa que você pode fazer, dependendo da sua arquitetura, é usar um ViewModel compartilhado entre os fragmentos. Portanto, no meu caso, FragmentA é um formulário e FragmentB é uma visualização de seleção de item onde o usuário pode pesquisar e selecionar um item, armazenando-o no ViewModel. Então, quando eu volto para FragmentA, a informação já está armazenada!

Arjun
fonte