Bedingte Navigation

Wenn Sie die Navigation für Ihre App entwerfen, möchten Sie möglicherweise zu einem Ziel basierend auf bedingter Logik mit einem anderen Ziel vergleichen. Beispiel: Ein Nutzer Sie folgen einem Deeplink zu einem Ziel, bei dem der Nutzer angemeldet sein muss. Oder ihr habt verschiedene Ziele in einem Spiel, wenn der Spieler gewinnt oder verliert.

Nutzeranmeldung

In diesem Beispiel versucht eine nutzende Person, zu einem Profilbildschirm zu navigieren, auf dem Authentifizierung. Da für diese Aktion eine Authentifizierung erforderlich ist, sollte der Nutzer werden zu einem Anmeldebildschirm weitergeleitet, sofern sie nicht bereits authentifiziert sind.

Die Navigationsgrafik für dieses Beispiel könnte etwa so aussehen:

<ph type="x-smartling-placeholder">
</ph> erfolgt die Anmeldung unabhängig vom Haupt-
            Navigationsfluss zu beseitigen.
Abbildung 1: Ein Log-in-Ablauf wird unabhängig vom Hauptnavigationsablauf der App.

Zur Authentifizierung muss die App die login_fragment aufrufen, wobei der Nutzer kann zur Authentifizierung einen Nutzernamen und ein Passwort eingeben. Wenn sie akzeptiert wird, ist der Nutzer an den profile_fragment-Bildschirm zurückgesendet. Wenn sie nicht akzeptiert wird, ist der Nutzer mit einem Hinweis, dass ihre Anmeldedaten ungültig sind, Snackbar Wenn Nutzende ohne Anmeldung zum Profilbildschirm zurückkehren, an den main_fragment-Bildschirm gesendet.

Hier ist das Navigationsdiagramm für diese App:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav_graph"
        app:startDestination="@id/main_fragment">
    <fragment
            android:id="@+id/main_fragment"
            android:name="com.google.android.conditionalnav.MainFragment"
            android:label="fragment_main"
            tools:layout="@layout/fragment_main">
        <action
                android:id="@+id/navigate_to_profile_fragment"
                app:destination="@id/profile_fragment"/>
    </fragment>
    <fragment
            android:id="@+id/login_fragment"
            android:name="com.google.android.conditionalnav.LoginFragment"
            android:label="login_fragment"
            tools:layout="@layout/login_fragment"/>
    <fragment
            android:id="@+id/profile_fragment"
            android:name="com.google.android.conditionalnav.ProfileFragment"
            android:label="fragment_profile"
            tools:layout="@layout/fragment_profile"/>
</navigation>

MainFragment enthält eine Schaltfläche, auf die der Nutzer klicken kann, um sein Profil aufzurufen. Wenn der Nutzer den Profilbildschirm sehen möchte, muss er sich zuerst authentifizieren. Dieses Interaktion wird anhand von zwei separaten Fragmenten modelliert, hängt aber von gemeinsamen Nutzerstatus. Für diese Statusinformationen ist weder die Verantwortung diese beiden Fragmente und ist passender in einem gemeinsamen UserViewModel enthalten. Dieser ViewModel wird von den Fragmenten gemeinsam genutzt, indem er zur Aktivität gehört. mit dem ViewModelStoreOwner implementiert wird. Im folgenden Beispiel requireActivity() wird in MainActivity aufgelöst, da MainActivity Hosts ProfileFragment:

Kotlin

class ProfileFragment : Fragment() {
    private val userViewModel: UserViewModel by activityViewModels()
    ...
}

Java

public class ProfileFragment extends Fragment {
    private UserViewModel userViewModel;
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);
        ...
    }
    ...
}

Die Nutzerdaten in UserViewModel werden über LiveData offengelegt. Um zu entscheiden, wo sollten Sie diese Daten im Auge behalten. Wenn Sie nach dem Aufrufen ProfileFragment, die App zeigt eine Willkommensnachricht an, wenn die Nutzerdaten vorhanden ist. Wenn es sich bei den Nutzerdaten um null handelt, gehen Sie zu LoginFragment, da sich der Nutzer authentifizieren muss, bevor er sein Profil sehen kann. Definieren Sie die Entscheidungslogik in ProfileFragment, wie im folgenden Beispiel gezeigt:

Kotlin

class ProfileFragment : Fragment() {
    private val userViewModel: UserViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val navController = findNavController()
        userViewModel.user.observe(viewLifecycleOwner, Observer { user ->
            if (user != null) {
                showWelcomeMessage()
            } else {
                navController.navigate(R.id.login_fragment)
            }
        })
    }

    private fun showWelcomeMessage() {
        ...
    }
}

Java

public class ProfileFragment extends Fragment {
    private UserViewModel userViewModel;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);
        final NavController navController = Navigation.findNavController(view);
        userViewModel.user.observe(getViewLifecycleOwner(), (Observer<User>) user -> {
            if (user != null) {
                showWelcomeMessage();
            } else {
                navController.navigate(R.id.login_fragment);
            }
        });
    }

    private void showWelcomeMessage() {
        ...
    }
}

Wenn die Nutzerdaten null sind, wenn sie ProfileFragment erreichen, sind sie an LoginFragment weitergeleitet.

Sie können NavController.getPreviousBackStackEntry() zum Abrufen von NavBackStackEntry für das vorherige Ziel, das die NavController-spezifischen Bundesland für das Ziel. LoginFragment verwendet die SavedStateHandle von vorherige NavBackStackEntry, um einen Anfangswert festzulegen, der angibt, ob die Nutzer hat sich erfolgreich angemeldet. Diesen Bundesstaat würden wir zurückgeben, sofort auf die Zurück-Taste des Systems zu drücken. Festlegen dieses Status Mit SavedStateHandle wird sichergestellt, dass der Status bis zum Ende des Prozesses bestehen bleibt.

Kotlin

class LoginFragment : Fragment() {
    companion object {
        const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
    }

    private val userViewModel: UserViewModel by activityViewModels()
    private lateinit var savedStateHandle: SavedStateHandle

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
        savedStateHandle.set(LOGIN_SUCCESSFUL, false)
    }
}

Java

public class LoginFragment extends Fragment {
    public static String LOGIN_SUCCESSFUL = "LOGIN_SUCCESSFUL"

    private UserViewModel userViewModel;
    private SavedStateHandle savedStateHandle;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);

        savedStateHandle = Navigation.findNavController(view)
                .getPreviousBackStackEntry()
                .getSavedStateHandle();
        savedStateHandle.set(LOGIN_SUCCESSFUL, false);
    }
}

Sobald der Nutzer einen Nutzernamen und ein Passwort eingegeben hat, werden diese UserViewModel für die Authentifizierung. Wenn die Authentifizierung erfolgreich war, UserViewModel speichert die Nutzerdaten. LoginFragment aktualisiert dann die LOGIN_SUCCESSFUL-Wert für SavedStateHandle und hebt sich von Back Stacks.

Kotlin

class LoginFragment : Fragment() {
    companion object {
        const val LOGIN_SUCCESSFUL: String = "LOGIN_SUCCESSFUL"
    }

    private val userViewModel: UserViewModel by activityViewModels()
    private lateinit var savedStateHandle: SavedStateHandle

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        savedStateHandle = findNavController().previousBackStackEntry!!.savedStateHandle
        savedStateHandle.set(LOGIN_SUCCESSFUL, false)

        val usernameEditText = view.findViewById(R.id.username_edit_text)
        val passwordEditText = view.findViewById(R.id.password_edit_text)
        val loginButton = view.findViewById(R.id.login_button)

        loginButton.setOnClickListener {
            val username = usernameEditText.text.toString()
            val password = passwordEditText.text.toString()
            login(username, password)
        }
    }

    fun login(username: String, password: String) {
        userViewModel.login(username, password).observe(viewLifecycleOwner, Observer { result ->
            if (result.success) {
                savedStateHandle.set(LOGIN_SUCCESSFUL, true)
                findNavController().popBackStack()
            } else {
                showErrorMessage()
            }
        })
    }

    fun showErrorMessage() {
        // Display a snackbar error message
    }
}

Java

public class LoginFragment extends Fragment {
    public static String LOGIN_SUCCESSFUL = "LOGIN_SUCCESSFUL"

    private UserViewModel userViewModel;
    private SavedStateHandle savedStateHandle;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        userViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);

        savedStateHandle = Navigation.findNavController(view)
                .getPreviousBackStackEntry()
                .getSavedStateHandle();
        savedStateHandle.set(LOGIN_SUCCESSFUL, false);

        EditText usernameEditText = view.findViewById(R.id.username_edit_text);
        EditText passwordEditText = view.findViewById(R.id.password_edit_text);
        Button loginButton = view.findViewById(R.id.login_button);

        loginButton.setOnClickListener(v -> {
            String username = usernameEditText.getText().toString();
            String password = passwordEditText.getText().toString();
            login(username, password);
        });
    }

    private void login(String username, String password) {
        userViewModel.login(username, password).observe(viewLifecycleOwner, (Observer<LoginResult>) result -> {
            if (result.success) {
                savedStateHandle.set(LOGIN_SUCCESSFUL, true);
                NavHostFragment.findNavController(this).popBackStack();
            } else {
                showErrorMessage();
            }
        });
    }

    private void showErrorMessage() {
        // Display a snackbar error message
    }
}

Die gesamte Logik für die Authentifizierung befindet sich UserViewModel Das ist wichtig, da es nicht in der Verantwortung der LoginFragment oder ProfileFragment, um zu ermitteln, wie Nutzer authentifiziert. Durch die Kapselung der Logik in einem ViewModel ist es nicht nur möglich, einfacher zu teilen und gleichzeitig zu testen. Wenn Ihre Navigationslogik komplex ist, sollten Sie diese Logik besonders durch Tests überprüfen. Weitere Informationen finden Sie in der Leitfaden zur Anwendungsarchitektur mit weiteren Informationen zu Strukturieren der App-Architektur um testbare Komponenten.

Im ProfileFragment ist der LOGIN_SUCCESSFUL-Wert, der in der SavedStateHandle kann in der onCreate() . Wenn der Nutzer zur ProfileFragment zurückkehrt, wird der LOGIN_SUCCESSFUL wird geprüft. Wenn der Wert false ist, kann der Nutzer zurückgeleitet werden. zu MainFragment.

Kotlin

class ProfileFragment : Fragment() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val navController = findNavController()

        val currentBackStackEntry = navController.currentBackStackEntry!!
        val savedStateHandle = currentBackStackEntry.savedStateHandle
        savedStateHandle.getLiveData<Boolean>(LoginFragment.LOGIN_SUCCESSFUL)
                .observe(currentBackStackEntry, Observer { success ->
                    if (!success) {
                        val startDestination = navController.graph.startDestination
                        val navOptions = NavOptions.Builder()
                                .setPopUpTo(startDestination, true)
                                .build()
                        navController.navigate(startDestination, null, navOptions)
                    }
                })
    }

    ...
}

Java

public class ProfileFragment extends Fragment {
    ...

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        NavController navController = NavHostFragment.findNavController(this);

        NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry();
        SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
        savedStateHandle.getLiveData(LoginFragment.LOGIN_SUCCESSFUL)
                .observe(navBackStackEntry, (Observer<Boolean>) success -> {
                    if (!success) {
                        int startDestination = navController.getGraph().getStartDestination();
                        NavOptions navOptions = new NavOptions.Builder()
                                .setPopUpTo(startDestination, true)
                                .build();
                        navController.navigate(startDestination, null, navOptions);
                    }
                });
    }

    ...
}

Wenn der Nutzer sich erfolgreich angemeldet hat, zeigt das ProfileFragment eine Willkommensnachricht.

Mit der hier verwendeten Technik können Sie zwischen zwei verschiedenen Fällen:

  • Im ersten Fall, in dem der Nutzer nicht angemeldet ist und aufgefordert werden sollte, Log-in.
  • Der Nutzer ist nicht angemeldet, weil er sich nicht angemeldet hat (aus false.

Wenn Sie diese Anwendungsfälle voneinander unterscheiden, vermeiden Sie es, wiederholt nach Fragen zu fragen. um sich anzumelden. Die Geschäftslogik für den Umgang mit Fehlern bleibt Ihnen überlassen und könnte die Anzeige eines Overlays beinhalten, das erklärt, warum die Nutzenden sich anmelden, die gesamte Aktivität abschließen oder den Nutzer an ein Ziel weiterleiten. für die keine Anmeldung erforderlich ist, wie es im vorherigen Codebeispiel der Fall war.