درباره ی پروژه
در این قسمت میخوایم از databinding و navigation به صورت عملی در یک پروژه استفاده کنیم.
این پروژه دارای دو Fragment و یک Activity است نقطه ی شروع LoginFragment است کاربر با وارد کردن نام کاربری و رمز عبور از LoginFragment به ProfileFragment منتقل میشه در ProfileFragment نام کاربری و آیدی تصادفی ساخته شده برای خودشو میبینه و همچنین میتونه از صفحه ی ProfileFragment خارج بشه و دوباره Login کنه.
توجه:
برای فعالسازی و اضافه کردن DataBinding و Navigation در پروژه به لینک های زیر برید.
خب دیگه شروع کنیم!
ابتدا در اندروید استادیو یه پروژه ایجاد کنید به اسم LoginDemo اسم پکیج پروژه رو com.kodedevel.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 به صورت خودکارساخته میشن پس از این قسمت عبور میکنیم.
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>
<?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
برای جابه جایی کاربر بین دو فرگمنت از کلاس 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 پیغام خروج کاربر رو نمایش دادیم.
اینم آخر این داستان سه بخشی ما!
دلم میخواد یه جوری با کد نویسی پولدار بشید که بیل گیتس پیشتون لنگ بندازه!
🌸البته قبلش خوش بخت باشید🌸