استفاده از Data Binding و Navigation در یک پروژه ی اندرویدی

پروژه ی عملی استفاده از دیتا بایندینگ و نویگیشن در اندروید

درباره ی پروژه

در این قسمت میخوایم از databinding و navigation به صورت عملی در یک پروژه استفاده کنیم.

این پروژه دارای دو Fragment و یک Activity است نقطه ی شروع LoginFragment است کاربر با وارد کردن نام کاربری و رمز عبور از LoginFragment به ProfileFragment منتقل میشه در ProfileFragment نام کاربری و آیدی تصادفی ساخته شده برای خودشو میبینه و همچنین میتونه از صفحه ی ProfileFragment خارج بشه و دوباره Login کنه.

توجه:

برای فعالسازی و اضافه کردن DataBinding و Navigation در پروژه به لینک های زیر برید.

Data Binding Navigation

خب دیگه شروع کنیم!

ابتدا در اندروید استادیو یه پروژه ایجاد کنید به اسم LoginDemo اسم پکیج پروژه رو com.skybirdbits.logindemo بزارید

قبل از هر کاری فایل strings را به صورت زیر ویرایش کنید

RootProject -> app -> main -> res -> values

strings.xml

<resources> <string name="app_name">Login Demo</string> <string name="login_label_description">Please login to your profile</string> <string name="welcome">Welcome</string> <string name="logout">Logout</string> <string name="id">Id:</string> <string name="user">User:</string> <string name="login">Login</string> </resources>

ایجاد فایل های پروژه و ویرایش آنها

در پروژه فایل های زیر را ایجاد و ویرایش میکنیم

Activity:

MainActivity.kt

activity_main.xml

توجه:

هنگام ایجاد پروژه اگه گزینه ی EmptyActivity رو بزنید فایل های Activity به صورت خودکارساخته میشن پس از این قسمت عبور میکنیم.

Fragments:

LoginFragment.kt

fragment_login.xml

ProfileFragment.kt

fragment_profile.xml

NavGraph:

main_navgraph.xml

۱- ایجاد ویو های فرگمنت :

در مسیر زیر فایل های xml مورد نظر رو ایجاد کنید:

RootProject -> app -> main -> res -> layout

fragment_login.xml

<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:layout_margin="16dp" android:text="@string/login_label_description" android:gravity="center" /> <EditText android:id="@+id/username_input" android:layout_width="280dp" android:layout_height="wrap_content" android:inputType="textAutoComplete" android:layout_margin="8dp" /> <EditText android:id="@+id/password_input" android:layout_width="280dp" android:layout_height="wrap_content" android:inputType="textPassword" android:layout_margin="8dp" app:hintEnabled="true" app:helperText="Input" /> <Button android:id="@+id/login_button" android:layout_width="280dp" android:layout_height="wrap_content" android:layout_margin="8dp" android:text="@string/login" /> </LinearLayout> </layout>

fragment_profile.xml

<?xml version="1.0" encoding="utf-8"?> <layout> <data> <variable name="username" type="String" /> <variable name="userId" type="String" /> </data> <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/welcome_text_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:text="@string/welcome" android:textSize="32sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/id_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="8dp" android:text="@string/id" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/welcome_text_label" /> <TextView android:id="@+id/textview_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:padding="8dp" android:text="@{userId}" android:textSize="16sp" app:layout_constraintStart_toEndOf="@id/id_label" app:layout_constraintTop_toBottomOf="@id/welcome_text_label" tools:text="text" /> <TextView android:id="@+id/username_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="8dp" android:text="@string/user" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/id_label" /> <TextView android:id="@+id/textview_username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:fontFamily="monospace" android:padding="8dp" android:text="@{username}" android:textSize="16sp" app:layout_constraintStart_toEndOf="@id/username_label" app:layout_constraintTop_toBottomOf="@id/id_label" tools:text="Rawhide95" /> <com.google.android.material.button.MaterialButton android:id="@+id/logout_button" style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/logout" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textview_id" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>

توضیح:

در این دو فایل از عنصر layout به عنوان روت فایل ها استفاده کردیم که بیانگر استفاده از databinding است سپس با استفاده از عنصر data داده های مورد نظر که باید view ها بهشون وصل بشن رو تعریف کردیم

در fragment_profile ویو های مورد نظر رو به داده های تعریف شده متصل کردیم

@{username}

@{userId}

۲-ایجاد کلاس های فرگمنت

در مسیر زیر دو کلاس زیر رو ایجاد کنید:

RootProject -> app -> main -> java -> com.skybirdbits.logindemo

LoginFragment.kt

package com.skybirdbits.logindemo import androidx.fragment.app.Fragment class LoginFragment: Fragment() { }

ProfileFragment.kt

package com.skybirdbits.logindemo import androidx.fragment.app.Fragment class ProfileFragment: Fragment() { }

۳- ایجاد فایل NavGraph :

در مسیر زیر فایل navgraph رو ایجاد کنید و کد هاشو به صورت زیر تغییر بدید:

RootProject -> app -> main -> res -> navigation

main_nav_graph.xml

<?xml version="1.0" encoding="utf-8"?> <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/main_nav_graph" app:startDestination="@id/loginFragment"> <fragment android:id="@+id/loginFragment" android:name="com.skybirdbits.logindemo.LoginFragment" android:label="LoginFragment" tools:layout="@layout/fragment_login"> <action android:id="@+id/from_loginFragment_to_profileFragment" app:destination="@id/profileFragment" app:popUpTo="@id/loginFragment" app:popUpToInclusive="true"> <argument android:name="usrname" app:argType="string" app:nullable="false" /> <argument android:name="id" app:argType="string" app:nullable="false"/> </action> </fragment> <fragment android:id="@+id/profileFragment" android:name="com.skybirdbits.logindemo.ProfileFragment" android:label="ProfileFragment" tools:layout="@layout/fragment_profile" > <action android:id="@+id/from_profileFragment_to_loginFragment" app:destination="@id/loginFragment" app:popUpTo="@id/profileFragment" app:popUpToInclusive="true" /> </fragment> </navigation>

توضیح:

در فایل بالا برای هرکدوم از فرگمنت ها یک عنصر fragment تعریف کردیم

با استفاده از عنصر action مسیر های بین این دو فرگمنت رو تعریف کردیم

عناصر argument به عنوان فرزند عنصر action در loginFragment حامل مقادیری هستند که میخوایم هنگام رفتن کاربر از LoginFragment به ProfileFragment منتقل بشه

توضیح مختصر از فیلد popUpTo در عنصر اکشن:

فرض کنید به صورت زیر سه تا فرگمنت داریم و کاربر بینشون جا به جا شده

A -> B -> C

میخوایم بعد از رفتن کاربر از B به C با زدن دکمه ی back بدون برگشتن به B به A برگرده به عبارتی فرگمنت B رو در Stack حذف کنیم در این صورت برای عنصر action مورد نظر که مسیر B به C رو تعریف کرده Id فرگمنت A رو به عنوان popUpTo تعریف میکنیم

۴-تعریف عنصر FragmentContainerView به عنوان NavHost در activity_main

فایل activity_main رو به صورت زیر ویرایش کنید:

<?xml version="1.0" encoding="utf-8"?> <merge 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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/main_nav_graph" app:defaultNavHost="true" /> </merge>

توضیح:

-از FragmentContainerView به عنوان navhost استفاده کردیم

- در فیلد name اسم کلاس NavHostFragment رو وارد کردیم تا FragmentContainerView به عنوان NavHostFragment شناخته بشه

-در فیلد navGraph فایل main_nav_graph رو به عنوان NavGraph مورد استفاده قرار دادیم

-در فیلد defaultNavhost چ با قرار دادن true گفتیم که عنصر FragmentContainerView به عنوان NavHost پیشفرض در نظر گرفته بشه

۵- وصل کردن view ها به کلاس های فرگمنت و استفاده از NavController برای جابه جایی کاربر بین دو فرگمنت:

کلاس LoginFragment رو به صورت زیر ویرایش کنید:

LoginFragment.kt

package com.skybirdbits.logindemo import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import com.skybirdbits.logindemo.databinding.FragmentLoginBinding import kotlin.random.Random class LoginFragment: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val binding = FragmentLoginBinding.inflate(inflater, container , false) val usernameInput = binding.usernameInput val passwordInput = binding.passwordInput binding.loginButton.setOnClickListener { val username = usernameInput.text.toString() val password = passwordInput.text.toString() val id = Random(1000) if(password.isNotBlank() && username.isNotBlank()) { Snackbar.make(binding.root, "Welcome $username" , Snackbar.LENGTH_SHORT).show() val action = LoginFragmentDirections.fromLoginFragmentToProfileFragment(username, id.nextLong().toString()) findNavController().navigate(action) }else { Toast.makeText(context , "Username and password should not be empty!", Toast.LENGTH_SHORT).show() } } return binding.root } }

توضیح:

-با توجه به عنصر layout که در فایل fragment_login تعریف کرده بودیم کلاس FragmentLoginBinding به صورت خودکار ایجاد شده با استفاده از این کلاس فایل fragment_login رو inflate کردیم سپس view ها رو به صورت مستقیم صدا زدیم

-دو متغیر usernameInput و passwordInput همون ویو های داخل fragment_login هستن در واقع از روی id انها٬ به صورت خودکار ساخته شده اند و مستقیم به خود view ها اشاره دارن

-با کلاس Random یک متغیر تصادفی از جنس Long ایجاد کردیم که به عنوان آی دی کاربر در نظر گرفته میشه سپس با شرط بررسی کردیم اگه usernameInput و passwordInput خالی نبودن

-با Snackbar پیغام ورود موفقیت امیز به کاربر بده و کاربر رو به ProfileFragment منتقل کنه

-متد fromLoginFragmentToProfileFragment رو به متد navigate پاس دادیم تا کاربر به ProfileFragment منتقل بشه؛ این متد بر اساس id عنصر اکشن در loginFragment ایجاد شده

کلاس ProfileFragment رو به صورت زیر ویرایش کنید:

ProfileFragment.kt

package com.skybirdbits.logindemo import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import com.skybirdbits.logindemo.databinding.FragmentProfileBinding class ProfileFragment: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val binding = FragmentProfileBinding.inflate(inflater, container, false) val username = requireArguments().getString("usrname") val id = requireArguments().getString("id") binding.username = username binding.userId = id binding.logoutButton.setOnClickListener { val action = ProfileFragmentDirections.fromProfileFragmentToLoginFragment() findNavController().navigate(action) Snackbar.make(binding.root,"You've been logged out", Snackbar.LENGTH_SHORT).show() } return binding.root } }

توضیح:

-با توجه به عنصر layout که در فایل fragment_profile تعریف کرده بودیم کلاس FragmentProfileBinding به صورت خودکار ایجاد شده با استفاده از این کلاس فایل fragment_profile رو inflate کردیم

-در کلاس بالا همونطور که مشخصه با استفاده از متد requireArguments داده های حمل شده توسط عناصر argument در فایل NavGraph رو در ProfileFragment تحویل گرفتیم.

-با استفاده از binding.username و binding.id متغیر های username و id تعریف شده در عنصر data در xml مورد نظر رو مقدارهی کردیم.

-متد fromProfileFragmentToLoginFragment رو داخل navigate پاس دادیم تا به وسیلش کاربر از ProfileFragment به LoginFragment منتقل بشه این متد بر اساس id عنصر action مربوط به profileFragmnet به صورت خودکار ساخته شده.

-با Snackbar پیغام خروج کاربر رو نمایش دادیم.

اینم آخر این داستان سه بخشی ما!

دلم میخواد یه جوری با کد نویسی پولدار بشید که بیل گیتس پیشتون لنگ بندازه!

🌸البته قبلش خوش بخت باشید🌸

arrow_drop_up
کپی شد!