Koşullu gezinme

Uygulamanız için gezinmeyi tasarlarken koşullu mantık doğrultusunda bir hedefe gitmek yerine diğerine gitmek isteyebilirsiniz. Örneğin, bir kullanıcı, giriş yapmasını gerektiren bir hedefe giden derin bağlantıyı izleyebilir veya bir oyunda oyuncunun kazandığı ya da kaybettiği durumlar için farklı hedefleriniz olabilir.

Kullanıcı girişi

Bu örnekte, bir kullanıcı kimlik doğrulama gerektiren bir profil ekranına gitmeyi dener. Bu işlem kimlik doğrulama gerektirdiğinden, kullanıcı kimliği önceden doğrulanmamışsa giriş ekranına yönlendirilmelidir.

Bu örnekteki gezinme grafiği aşağıdaki gibi görünebilir:

Giriş akışı, uygulamanın ana gezinme akışından bağımsız olarak yürütülür.
Şekil 1. Giriş akışı, uygulamanın ana gezinme akışından bağımsız olarak gerçekleştirilir.

Kimlik doğrulama için uygulamanın login_fragment sayfasına gitmesi gerekir. Burada kullanıcı, kimliği doğrulamak için kullanıcı adı ve şifre girmelidir. Kabul edilirse kullanıcı tekrar profile_fragment ekranına gönderilir. Kabul edilmezse kullanıcıya bir Snackbar kullanılarak kimlik bilgilerinin geçersiz olduğu bildirilir. Kullanıcı, giriş yapmadan profil ekranına geri dönerse main_fragment ekranına yönlendirilir.

Bu uygulamanın gezinme grafiği şöyledir:

<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, kullanıcının profilini görüntülemek için tıklayabileceği bir düğme içerir. Kullanıcı, profil ekranını görmek istiyorsa önce kimliğini doğrulaması gerekir. Bu etkileşim iki ayrı parça kullanılarak modellenir ancak paylaşılan kullanıcı durumuna bağlıdır. Bu durum bilgisi, bu iki parçanın herhangi birinin sorumluluğunda değildir ve paylaşılan bir UserViewModel içinde daha uygun bir şekilde tutulur. Bu ViewModel, ViewModelStoreOwner işlemini uygulayan etkinliğe göre kapsama alınarak parçalar arasında paylaşılır. Aşağıdaki örnekte, MainActivity ProfileFragment barındırdığı için requireActivity(), MainActivity olarak çözümlenir:

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

UserViewModel içindeki kullanıcı verileri LiveData üzerinden açığa çıkarılır. Bu nedenle, nereye gideceğinize karar vermek için bu verileri gözlemlemeniz gerekir. ProfileFragment uygulamasına gittiğinde, kullanıcı verileri mevcutsa uygulama bir karşılama mesajı gösterir. Kullanıcı verileri null ise kullanıcının profilini görmeden önce kimlik doğrulaması yapması gerektiğinden LoginFragment sayfasına gidersiniz. Karar verme mantığını ProfileFragment öğenizde aşağıdaki örnekte gösterildiği gibi tanımlayın:

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

ProfileFragment hedefine ulaşan kullanıcı verileri null ise LoginFragment öğesine yönlendirilir.

Önceki hedefin NavBackStackEntry durumunu almak için NavController.getPreviousBackStackEntry() kullanabilirsiniz. Bu değer, hedef için NavController'a özgü durumu içerir. LoginFragment, kullanıcının başarıyla giriş yapıp yapmadığını gösteren bir başlangıç değeri ayarlamak için önceki NavBackStackEntry öğesinin SavedStateHandle değerini kullanır. Kullanıcı hemen sistem geri düğmesine basarsa, geri dönmek isteyeceğimiz durum budur. Bu durumu SavedStateHandle kullanarak ayarlamak, işlemin tamamlanabilmesini sağlayarak bu durumun devam etmesini sağlar.

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

Kullanıcı, kullanıcı adı ve şifre girdikten sonra kimlik doğrulama için UserViewModel aracına iletilir. Kimlik doğrulama başarılı olursa UserViewModel, kullanıcı verilerini depolar. LoginFragment daha sonra SavedStateHandle üzerindeki LOGIN_SUCCESSFUL değerini günceller ve kendini arka yığından çıkarır.

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

Kimlik doğrulama ile ilgili tüm mantığın UserViewModel kapsamında tutulduğunu unutmayın. Kullanıcıların kimliklerinin nasıl doğrulanacağını belirlemek LoginFragment veya ProfileFragment sorumluluğu olmadığından bu önemlidir. Mantığınızı bir ViewModel içine almanız, yalnızca paylaşmayı ve test etmeyi de kolaylaştırır. Gezinme mantığınız karmaşıksa bu mantığı özellikle test ederek doğrulamanız gerekir. Uygulamanızın mimarisini test edilebilir bileşenlere göre yapılandırma hakkında daha fazla bilgi için Uygulama mimarisi rehberine bakın.

ProfileFragment içinde, SavedStateHandle içinde depolanan LOGIN_SUCCESSFUL değeri onCreate() yönteminde gözlemlenebilir. Kullanıcı ProfileFragment öğesine döndüğünde LOGIN_SUCCESSFUL değeri kontrol edilir. Değer false ise kullanıcı tekrar MainFragment uygulamasına yönlendirilebilir.

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

    ...
}

Kullanıcı başarıyla giriş yaparsa ProfileFragment bir karşılama mesajı gösterir.

Sonucu kontrol etmek için burada kullanılan teknik, iki farklı durumu birbirinden ayırt etmenizi sağlar:

  • Kullanıcının giriş yapmadığı ve giriş yapması istenen ilk durum.
  • Kullanıcı, giriş yapmamayı seçtiğinden (false nedeniyle) giriş yapmamıştır.

Bu kullanım alanlarını birbirinden ayırt ederek sürekli olarak kullanıcıdan giriş yapmasını istemekten kaçınabilirsiniz. Hata durumlarını ele almaya ilişkin iş mantığı size bırakılır ve önceki kod örneğinde olduğu gibi, kullanıcının neden giriş yapması gerektiğini, tüm etkinliği tamamlamasını veya kullanıcıyı giriş gerektirmeyen bir hedefe yönlendirmeyi açıklayan bir yer paylaşımı görüntüleyebilir.