¿Existe algún método que funcione como fragmento de inicio para obtener resultados?

95

Actualmente tengo un fragmento en una superposición. Esto es para iniciar sesión en el servicio. En la aplicación del teléfono, cada uno de los pasos que quiero mostrar en la superposición son sus propias pantallas y actividades. Hay tres partes del proceso de inicio de sesión y cada una tenía su propia actividad a la que se llamaba con startActivityForResult ().

Ahora quiero hacer lo mismo usando fragmentos y una superposición. La superposición mostrará un fragmento correspondiente a cada actividad. El problema es que estos fragmentos están alojados en una actividad de la API Honeycomb. Puedo hacer que el primer fragmento funcione, pero luego necesito startActivityForResult (), lo cual no es posible. ¿Hay algo en la línea de startFragmentForResult () donde pueda iniciar un nuevo fragmento y cuando haya terminado, devolver un resultado al fragmento anterior?

CACuzcatlán
fuente

Respuestas:

58

Todos los Fragmentos viven dentro de Actividades. Iniciar un Fragmento para obtener un resultado no tiene mucho sentido, porque la Actividad que lo alberga siempre tiene acceso a él, y viceversa. Si el Fragmento necesita transmitir un resultado, puede acceder a su Actividad y establecer su resultado y finalizarlo. En el caso de intercambiar Fragmentos en una sola Actividad, ambos Fragmentos aún pueden acceder a la Actividad, y todos los mensajes que pasan simplemente pueden pasar por la Actividad.

Solo recuerde que siempre tiene comunicación entre un Fragmento y su Actividad. Comenzar y terminar con un resultado es el mecanismo de comunicación entre Actividades: las Actividades pueden delegar la información necesaria a sus Fragmentos.

LeffelMania
fuente
11
Además, al cargar un fragmento de otro, puede configurar el fragmento de destino y volver a llamar al onActivityResultmétodo del fragmento principal si así lo desea.
PJL
4
Su respuesta es incompleta, los fragmentos utilizan un ciclo de vida como actividades. Por lo tanto, no podemos simplemente pasar argumentos a través de métodos, pero debemos usar paquetes para pasar valores. Incluso si el fragmento estableció un valor en alguna parte, necesitamos saber cuándo terminó. ¿Deberían los fragmentos anteriores obtener el valor cuando se inicia / reanuda? Esta es una idea. Pero no hay una forma adecuada de almacenar el valor, el fragmento podría ser llamado por varios otros fragmentos / actividades.
Loenix
1
bueno, tendría sentido llamar 'iniciar fragmento por resultado' ya que daría mucha más flexibilidad -> ¿por qué el fragmento como niño debería conocer a su padre? No sería mucho más limpio para el fragmento hacer su trabajo y devolver el resultado independientemente de la actividad en la que viva. Especialmente en el contexto de aplicaciones de actividad única.
daneejela
¿Qué pasa con las posibles fugas de memoria?
daneejela
60

Si lo desea, existen algunos métodos de comunicación entre Fragmentos,

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

Puede devolver la llamada utilizando estos.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
nagoya0
fuente
No lo he confirmado, pero quizás funcione. por lo tanto, debe tener cuidado con las pérdidas de memoria causadas por referencias circulares por abuso de setTargetFragment().
nagoya0
3
¡La mejor solución hasta ahora! Funciona muy bien cuando se tiene una Actividad y una pila de fragmentos donde tratar de encontrar el fragmento correcto para notificar sería una pesadilla. Gracias
Damien Praca
1
@ userSeven7s esto no va a funcionar para reemplazar caso, pero debe trabajar para ocultar caso
Muhammad Babar
1
@ nagoya0 lo hace mejor si usamos WeakReference para el fragmento de destino
quangson91
Si bien esto funciona, setTargetFragmentactualmente está desaprobado. Vea setResultListeneren stackoverflow.com/a/61881149/2914140 aquí.
CoolMind
11

Simplemente podemos compartir el mismo 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
fuente
3
Hola Levon, creo que el código anterior no compartirá la misma instancia de modelo de vista. Estás pasando este (fragmento) para ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Esto creará dos instancias separadas para fragmentos. Debe pasar la actividad ViewModelProviders.of (actividad) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Buena captura, lo arreglaré ahora.
Levon Petrosyan
8

Recientemente, Google acaba de agregar una nueva capacidad FragmentManagerque hizo FragmentManagerque pueda actuar como un almacén central para los resultados de los fragmentos. Podemos pasar los datos de un fragmento a otro fácilmente.

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...
    }
}

Un fragmento de que queremos el resultado de vuelta.

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

El fragmento se tomó de los documentos oficiales de Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

A la fecha de redacción de esta respuesta, esta característica aún se encuentra en alphaestado. Puedes probarlo usando esta dependencia.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
fuente
4

Mis 2 centavos.

Cambio entre fragmentos intercambiando un fragmento antiguo por uno nuevo usando ocultar y mostrar / agregar (existente / nuevo). Entonces, esta respuesta es para desarrolladores que usan fragmentos como yo.

Entonces uso el onHiddenChanged método para saber que el fragmento antiguo se cambió del nuevo. Consulte el código a continuación.

Antes de dejar el nuevo fragmento, establezco un resultado en un parámetro global para ser consultado por el fragmento antiguo. Esta es una solución muy ingenua.

@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
fuente
¿Qué es el getAndReset()método?
EpicPandaForce
¿No se onResume()llama también al primer fragmento cuando se descarta el segundo?
PJ_Finnegan
2

En su fragmento puede llamar a getActivity (). Esto le dará acceso a la actividad que creó el fragmento. Desde allí, puede llamar a su método de personalización para establecer los valores o pasar los valores.

Summved Jain
fuente
1

Hay una biblioteca de Android, FlowR, que le permite iniciar fragmentos para obtener resultados.

Comenzar un fragmento para obtener un resultado.

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

El manejo da como resultado el fragmento de llamada.

@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");
        }
    }
}

Estableciendo el resultado en el Fragmento.

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

Una solución que utiliza interfaces (y Kotlin). La idea central es definir una interfaz de devolución de llamada, implementarla en su actividad y luego llamarla desde su fragmento.

Primero, crea una interfaz ActionHandler:

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

A continuación, llame a esto de su hijo (en este caso, su 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 esto en su padre para recibir la devolución de llamada (en este caso, su Actividad):

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
fuente
0

La forma más fácil de devolver datos es mediante setArgument (). Por ejemplo, tiene fragment1 que llama a fragment2 que llama a fragment3, fragment1 -> framgnet2 -> fargment3

En fragment1

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();
}

En fragment2 llamamos fragment3 como de costumbre

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();
}

Cuando terminamos nuestra tarea en fragment3 ahora llamamos así:

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);

Ahora en el fragment2 podemos llamar fácilmente a los argumentos

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
fuente
Tenga cuidado con este, podría funcionar en algunos casos, pero si su fragmento tenía argumentos originales ('fragmento 2' en este ejemplo), esto anularía los argumentos originales utilizados para crear el fragmento, lo que podría conducir a un estado inconsistente si el fragmento se destruye y se recrea.
Juan
0

Otra cosa que podría hacer dependiendo de su arquitectura es usar un ViewModel compartido entre los fragmentos. Entonces, en mi caso, FragmentA es un formulario, y FragmentB es una vista de selección de elementos donde el usuario puede buscar y seleccionar un elemento, almacenándolo en ViewModel. Luego, cuando regrese a FragmentA, ¡la información ya está almacenada!

Arjun
fuente