Existe-t-il une méthode qui fonctionne comme un fragment de départ pour le résultat?

93

J'ai actuellement un fragment dans une superposition. Ceci est pour vous connecter au service. Dans l'application pour téléphone, chacune des étapes que je souhaite afficher dans la superposition correspond à ses propres écrans et activités. Il y a 3 parties du processus de connexion et chacune avait sa propre activité qui a été appelée avec startActivityForResult ().

Maintenant, je veux faire la même chose en utilisant des fragments et une superposition. La superposition affichera un fragment correspondant à chaque activité. Le problème est que ces fragments sont hébergés dans une activité de l'API Honeycomb. Je peux faire fonctionner le premier fragment, mais je dois ensuite startActivityForResult (), ce qui n'est pas possible. Y a-t-il quelque chose du genre startFragmentForResult () où je peux lancer un nouveau fragment et quand c'est fait, il renvoie un résultat au fragment précédent?

CACuzcatlan
la source

Réponses:

57

Tous les fragments vivent à l'intérieur des activités. Démarrer un fragment pour un résultat n'a pas beaucoup de sens, car l'activité qui l'abrite y a toujours accès, et vice versa. Si le fragment a besoin de transmettre un résultat, il peut accéder à son activité, définir son résultat et le terminer. Dans le cas de l'échange de fragments dans une seule activité, l'activité est toujours accessible par les deux fragments, et tout votre message passant peut simplement passer par l'activité.

N'oubliez pas que vous avez toujours une communication entre un fragment et son activité. Commencer et finir avec un résultat est le mécanisme de communication entre les activités - Les activités peuvent alors déléguer toutes les informations nécessaires à leurs fragments.

LeffelMania
la source
11
De plus, lors du chargement d'un fragment à partir d'un autre, vous pouvez définir le fragment cible et rappeler la onActivityResultméthode du fragment parent si vous le souhaitez.
PJL
4
Votre réponse est incomplète, les fragments utilisent un cycle de vie comme des activités. Donc, nous ne pouvons pas simplement passer des arguments via des méthodes mais nous devrions utiliser des bundles pour passer des valeurs. Même si le fragment a défini une valeur quelque part, nous devons savoir quand il s'est terminé. Les fragments précédents devraient-ils obtenir la valeur au démarrage / à la reprise? C'est une idée. Mais il n'y a pas de moyen approprié de stocker la valeur, le fragment pourrait être appelé par plusieurs autres fragments / activités.
Loenix
59

Si vous le souhaitez, il existe des méthodes de communication entre Fragments,

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

Vous pouvez les rappeler en utilisant ces derniers.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
nagoya0
la source
Je ne l'ai pas confirmé, mais peut-être que ça marche. vous devez donc faire attention aux fuites de mémoire causées par une référence circulaire par abus de setTargetFragment().
nagoya0
3
La meilleure solution à ce jour! Fonctionne très bien lorsque vous avez une activité et une pile de fragments où essayer de trouver le bon fragment à notifier serait un cauchemar. Merci
Damien Praca
1
@ userSeven7s cela ne fonctionnera pas pour le cas de remplacement mais devrait fonctionner pour le cas de cache
Muhammad Babar
1
@ nagoya0 le fait mieux si nous utilisons WeakReference pour le fragment cible
quangson91
11

Nous pouvons simplement partager le même ViewModel entre les fragments

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
la source
3
Salut Levon, je pense que le code ci-dessus ne partagera pas la même instance de modèle de vue. Vous transmettez ce (fragment) pour ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Cela créera deux instances distinctes pour les fragments. Vous devez passer l'activité ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Shailendra Patil
@ShailendraPatil Belle prise, je vais le réparer maintenant.
Levon Petrosyan
4

Mes 2 cents.

Je passe entre les fragments en échangeant un ancien fragment avec un nouveau en utilisant masquer et afficher / ajouter (existant / nouveau). Donc, cette réponse est pour les développeurs qui utilisent des fragments comme moi.

Ensuite, j'utilise la onHiddenChangedméthode pour savoir que l'ancien fragment a été remplacé par le nouveau. Voir le code ci-dessous.

Avant de quitter le nouveau fragment, j'ai défini un résultat dans un paramètre global à interroger par l'ancien fragment. C'est une solution très naïve.

@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
la source
Qu'est-ce que la getAndReset()méthode?
EpicPandaForce
N'est-ce pas aussi onResume()appelé sur le premier fragment lorsque le second est écarté?
PJ_Finnegan
4

Récemment, Google vient d'ajouter une nouvelle capacité FragmentManagerqui FragmentManagerlui permet de servir de magasin central pour les résultats de fragments. Nous pouvons facilement passer les données entre les fragments.

Fragment de départ.

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 fragment dont nous voulons le résultat.

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

L'extrait est tiré des documents officiels de Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

Aux données de cette réponse écrites, cette fonctionnalité est toujours en alphaétat. Vous pouvez l'essayer en utilisant cette dépendance.

androidx.fragment:fragment:1.3.0-alpha05
Boonya Kitpitak
la source
2

Dans votre fragment, vous pouvez appeler getActivity (). Cela vous donnera accès à l'activité qui a créé le fragment. À partir de là, vous pouvez appeler votre méthode de personnalisation pour définir les valeurs ou pour transmettre les valeurs.

Jain sommé
la source
1

Il existe une bibliothèque Android - FlowR qui vous permet de démarrer des fragments pour obtenir des résultats.

Commencer un fragment pour le résultat.

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

La gestion entraîne le fragment appelant.

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

Définition du résultat dans le Fragment.

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

Une solution utilisant des interfaces (et Kotlin). L'idée de base est de définir une interface de callback, de l'implémenter dans votre activité, puis de l'appeler depuis votre fragment.

Tout d'abord, créez une interface ActionHandler:

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

Ensuite, appelez ceci depuis votre enfant (dans ce cas, votre fragment):

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

Enfin, implémentez ceci dans votre parent pour recevoir le rappel (dans ce cas, votre activité):

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
la source
0

Le moyen le plus simple de renvoyer des données est d'utiliser setArgument (). Par exemple, vous avez fragment1 qui appelle fragment2 qui appelle fragment3, fragment1 -> framgnet2 -> fargment3

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

Dans fragment2, nous appelons fragment3 comme d'habitude

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

Lorsque nous avons terminé notre tâche dans fragment3, nous appelons maintenant comme ceci:

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

Maintenant, dans fragment2, nous pouvons facilement appeler des arguments

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
la source
Faites attention avec celui-ci, cela pourrait fonctionner dans certains cas, mais si votre fragment avait des arguments originaux ('fragment 2' dans cet exemple), cela remplacerait les arguments originaux utilisés pour créer le fragment, ce qui pourrait conduire à un état incohérent si le fragment est détruit et recréé.
Juan
0

Une autre chose que vous pouvez faire en fonction de votre architecture est d'utiliser un ViewModel partagé entre les fragments. Donc, dans mon cas, FragmentA est un formulaire et FragmentB est une vue de sélection d'éléments dans laquelle l'utilisateur peut rechercher et sélectionner un élément, en le stockant dans le ViewModel. Puis quand je reviens sur FragmentA, les informations sont déjà stockées!

Arjun
la source