
يعد التنقل في تطوير تطبيقات Android أمرًا مهمًا ويجب أن تفكر مرتين في أكثر ما يناسب المكتبة (أو الحل الخاص بك) وكيف سيكون مناسبًا للاستخدام عندما يصبح التطبيق أكبر. أيضًا ، قد يكون من الجيد التفكير في مدى سهولة تغيير تطبيقك إلى تطبيق آخر.
قبل أن نبدأ ، اسمحوا لي أن أقول قصة. دعنا نسميها هكذا "كيف صنعنا وحدات المشروع ولماذا كرهت الملاحة لدينا."
كان لدينا مشروع وحدة واحدة وكل شيء سار على ما يرام باستثناء وقت البناء ، وذلك لأننا استخدمنا Dagger واستغرقنا وقتًا طويلاً لإنشاء مكون واحد. لذلك قررنا فصل المشروع إلى وحدات حسب الميزات ، على سبيل المثال ، ميزة تسجيل الدخول ، وميزة المساعدة ، إلخ.
واجهنا الكثير من الصعوبات لأننا اضطررنا إلى نقل منطق الشبكة إلى وحدة Gradle الخاصة به وتسجيل الدخول إلى المجال في وحدته وأيضًا لدينا مشاكل في التنقل.
في تطبيقنا ، كان لدينا جهاز توجيه يعرف كل جزء ، وكان مسؤولاً عن التحولات بينهما. لسوء الحظ ، عرفت كل جزء بوجود جهاز توجيه وعرف عن أجزاء أخرى ، لأن الجزء قال للموجه ما يجب فتح جزء منه أو أن الجزء ينتظر انتظار النتائج من جزء آخر. هذه الأشياء دمرت فكرة صنع وحدات ميزة مستقلة.
لذلك ، كنت أفكر في كيفية جعله أفضل ، وكيفية فصل هذه الروابط بين الأجزاء ، وكيفية التخلص من المعرفة بوجود بعض أجهزة التوجيه. في هذه المقالة ، سأبين ما صنعته.
ماذا سنفعل
سيكون التطبيق بسيطًا جدًا ولكنه سيكون معياريًا. ستكون هناك وحدة تطبيق واحدة وثلاث وحدات للميزات:
- وحدة التطبيقات - هذه الوحدة تعرف كل شيء عن التطبيق وهناك سنقوم بتطبيق الملاحة لدينا. سوف نستخدم مكون التنقل من JetPack.
- وحدة الأسئلة - هذه الوحدة مسؤولة عن ميزة الأسئلة. لا تعرف الوحدة شيئًا عن وحدة التطبيقات أو أي وحدة أخرى. سيكون هناك
QuestionsNavigation
وواجهة QuestionsNavigation
. - وحدة السؤال - سيكون هناك ميزة السؤال. كما الوحدة السابقة ، فإنه لا يعرف عن وحدات أخرى. ستحتوي هذه الوحدة على
QuestionNavigation
وواجهة QuestionNavigation
. - وحدة النتيجة - هذه هي ميزة النتيجة. أيضا ، هو وحدة مستقلة تماما. سيكون هناك
RightAnswerFragment
مع واجهة RightAnswerNavigation
وسيكون هناك WrongAnswerFragment
مع واجهة WrongAnswerNavigation
.
يمكن العثور على الكود المصدري للتطبيق هنا:
GitHub .
التنفيذ
في هذه المقالة ،
سأستخدم مكتبة
ComponentsManager للحصول على ميزة التنقل. في مشروع حقيقي ، سأوفر ميزة التنقل عن طريق
Dagger ، لكن ليس من الضروري لهذا المشروع الصغير. أيضًا ، تساعدك مكتبة
ComponentManager على استخدام
Dagger في مشاريع متعددة الوحدات.
هل انت جاهز دعنا نذهب!
وحدة الأسئلة
أنشئ وحدة أندرويد واسميها
الأسئلة . بعد ذلك ، تأكد من تكوين Kotlin في الوحدة النمطية وأن تتم إضافة
ConstraintLayout و
ComponentsManager تبعيات.
بدلاً من استخدام
Route
أو
Navigator
في
Fragment
،
Fragment
واجهة
QuestionsNavigation
تحدد التحولات المطلوبة. وبالتالي ، بالنسبة إلى Fragment ، لا يهم كيف سيتم تنفيذ تلك التحولات ، بل يحتاج فقط إلى تلك الواجهة ويعتمد عليها عند استدعاء الطريقة ، وستكون الشاشة المطلوبة مفتوحة.
interface QuestionsNavigation { fun openQuestion(questionId: Long) }
لفتح شاشة مع سؤال ، نحن فقط نسمي أسلوب
openQuestion(questionId: Long)
وهذا هو. نحن لا نهتم بكيفية فتح الشاشة ، ولا نهتم بما إذا كانت
Fragment
أو
Fragment
أو أي شيء آخر.
هنا هو
QuestionsFragment
وتخطيطه .
class QuestionsFragment : Fragment() { private val navigation: QuestionsNavigation by lazy { XInjectionManager.findComponent<QuestionsNavigation>() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_questions, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button_first_question.setOnClickListener { navigation.openQuestion(1) } button_second_question.setOnClickListener { navigation.openQuestion(2) } button_third_question.setOnClickListener { navigation.openQuestion(3) } } }
وحدة السؤال
أولاً ، قم بإنشاء وحدة مكتبة Android واسميها
السؤال . يجب أن يحتوي build.gradle الخاص بالوحدة النمطية على نفس التبعيات مثل ملف build.gradle الخاص بوحدة الأسئلة.
بعد ذلك نقوم بإنشاء واجهة تعرف التنقل في الوحدة. ستكون فكرة التنقل في الوحدات النمطية الأخرى كما في النموذج السابق.
من مستخدم
QuestionFragment
يمكن فتح شاشة إجابة خاطئة أو شاشة إجابة صحيحة حتى يكون للواجهة طريقتان.
interface QuestionNavigation { fun openWrongAnswer() fun openRightAnswer() }
آخر شيء هو شظية. بالنسبة إلى هذه الشريحة ، سنضيف طريقة مصاحبة تقوم بإرجاع
Bundle
حتى نتمكن من تمرير معرف السؤال واستخدامه.
تخطيط الجزء.
class QuestionFragment : Fragment() { companion object { private const val EXTRA_QUESTION_ID = "me.vponomarenko.modular.navigation.question.id" fun createBundle(questionId: Long) = Bundle().apply { putLong(EXTRA_QUESTION_ID, questionId) } } private val navigation: QuestionNavigation by lazy { XInjectionManager.findComponent<QuestionNavigation>() } private val questionId: Long by lazy { arguments?.getLong(EXTRA_QUESTION_ID) ?: throw IllegalStateException("no questionId") } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_question, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) text_question.text = getString(R.string.question, questionId) button_right_answer.setOnClickListener { navigation.openRightAnswer() } button_wrong_answer.setOnClickListener { navigation.openWrongAnswer() } } }
وحدة النتيجة
وحدة الميزة النهائية هي وحدة النتائج. سيكون هناك شظايا تظهر إجابة صحيحة وخاطئة. قم بإنشاء وحدة مكتبة Android وندعوها
بالنتيجة ، ثم قم بتغيير build.gradle للوحدة بحيث يكون لها نفس التبعيات مثل وحدات الميزات السابقة.
لنبدأ من جزء الإجابة الصحيحة. سيكون لواجهة التنقل طريقة واحدة لأن المستخدم يمكنه فتح شاشة أسئلة فقط.
interface RightAnswerNavigation { fun openAllQuestions() }
The
RightAnswerFragment
،
تخطيطه .
class RightAnswerFragment : Fragment() { private val navigation: RightAnswerNavigation by lazy { XInjectionManager.findComponent<RightAnswerNavigation>() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_right, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button_all_questions.setOnClickListener { navigation.openAllQuestions() } } }
كما قلت سابقًا ، تحتوي هذه الوحدة على
Fragments
، لذلك دعونا ننفذ شاشة إجابة خاطئة.
في تطبيقي ، من مستخدم شاشة الإجابة الخاطئة ، يمكن فقط الرجوع إلى شاشة السؤال ومحاولة الإجابة على السؤال مرة أخرى ، وبالتالي فإن واجهة التنقل لديها طريقة واحدة فقط.
interface WrongAnswerNavigation { fun tryAgain() }
و WrongAnswerFragment ،
تخطيطه .
class WrongAnswerFragment : Fragment() { private val navigation: WrongAnswerNavigation by lazy { XInjectionManager.findComponent<WrongAnswerNavigation>() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_wrong, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) button_try_again.setOnClickListener { navigation.tryAgain() } } }
وحدة التطبيق
لقد صنعنا الوحدات النمطية وحان الوقت لتوصيلها وتشغيل التطبيق.
أول شيء أولاً ، نحن بحاجة إلى تعديل build.gradle في وحدة التطبيق. يجب أن يحتوي على كافة الوحدات النمطية التي تم إنشاؤها ومكتبة مكون التنقل مع إدارة المكونات.
قبل تنفيذ الفئات التي تعمل مع الملاحة ، يتعين علينا إضافة التنقل نفسه. سوف نستخدم مكون التنقل لتصميمه.
قم بإنشاء ملف موارد تنقل
واستدعاه nav_graph.xml . سيكون هناك ثلاث اتصالات:
- the
QuestionFragment
مع QuestionFragment
- The
QuestionFragment
مع WrongAnswerFragment
- The
QuestionFragment
مع RightAnwerFragment
. هذا الصدد لديه اختلاف بسيط. إذا كان المستخدم على RightAnswerFragment
وكان المستخدم يضغط على زر الرجوع ، فسيتم إعادته إلى QuestionsFragment
. كيف يحدث ذلك؟ ما عليك rightAnswerFragment
اختيار السهم الذي يربط questionFragment
مع rightAnswerFragment
، ثم في القائمة المنسدلة بالقرب من Pop لتحديد questionsFragment
.
هنا هو تمثيل 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" android:id="@+id/nav_graph" app:startDestination="@id/questionsFragment"> <fragment android:id="@+id/questionsFragment" android:name="me.vponomarenko.modular.navigation.questions.QuestionsFragment" android:label="QuestionsFragment"> <action android:id="@+id/action_questionsFragment_to_questionFragment" app:destination="@id/questionFragment" /> </fragment> <fragment android:id="@+id/questionFragment" android:name="me.vponomarenko.modular.navigation.question.QuestionFragment" android:label="QuestionFragment"> <action android:id="@+id/action_questionFragment_to_wrongAnswerFragment" app:destination="@id/wrongAnswerFragment" /> <action android:id="@+id/action_questionFragment_to_rightAnswerFragment" app:destination="@id/rightAnswerFragment" app:popUpTo="@+id/questionsFragment" /> </fragment> <fragment android:id="@+id/wrongAnswerFragment" android:name="me.vponomarenko.modular.navigation.result.wrong.WrongAnswerFragment" android:label="WrongAnswerFragment" /> <fragment android:id="@+id/rightAnswerFragment" android:name="me.vponomarenko.modular.navigation.result.right.RightAnswerFragment" android:label="RightAnswerFragment" /> </navigation>
ثم قم بإنشاء فئة
Navigator
. من الواضح أن الفصل مسؤول عن إجراء انتقالات بين الشظايا. يقوم بتنفيذ جميع واجهات التنقل من الوحدات النمطية للميزات ، ولا تنس إضافة مكالمات من الإجراءات المقابلة لفتح الشاشة المطلوبة. أيضًا ، لدى الفصل طرق لربط
navController
.
class Navigator : QuestionsNavigation, QuestionNavigation, RightAnswerNavigation, WrongAnswerNavigation { private var navController: NavController? = null override fun openQuestion(questionId: Long) { navController?.navigate( R.id.action_questionsFragment_to_questionFragment, QuestionFragment.createBundle(questionId) ) } override fun openWrongAnswer() { navController?.navigate(R.id.action_questionFragment_to_wrongAnswerFragment) } override fun openRightAnswer() { navController?.navigate(R.id.action_questionFragment_to_rightAnswerFragment) } override fun openAllQuestions() { navController?.popBackStack() } override fun tryAgain() { navController?.popBackStack() } fun bind(navController: NavController) { this.navController = navController } fun unbind() { navController = null } }
في السطر
الثامن ، يمكنك رؤية كيفية تمرير معرف
QuestionFragment
إلى
QuestionFragment
.
بعد ذلك ، قم بإنشاء فصل
NavApplication
. في الواقع ، اضطررت إلى إضافة هذه الفئة لتوفير
Navigator
للفئات الأخرى ، وإلا ، سيكون من الصعب الحصول على المستكشف في الوحدات النمطية للميزات. لا تنس إضافة هذه الفئة إلى البيان.
class NavApplication : Application() { override fun onCreate() { super.onCreate() XInjectionManager.bindComponentToCustomLifecycle(object : IHasComponent<Navigator> { override fun getComponent(): Navigator = Navigator() }) } }
باستخدام الأسطر 4 إلى 6 ، سيكون من الممكن الحصول على تطبيقات واجهة التنقل الخاصة بالميزات عن طريق استدعاء XInjectionManager.findComponent ().
أخيرًا وليس آخرًا ، قم بتغيير فئة
MainActivity
وتخطيطها .
class MainActivity : AppCompatActivity() { private val navigator: Navigator by lazy { XInjectionManager.findComponent<Navigator>() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun onResume() { super.onResume() navigator.bind(findNavController(R.id.nav_host_fragment)) } override fun onPause() { super.onPause() navigator.unbind() } override fun onSupportNavigateUp(): Boolean = findNavController(R.id.nav_host_fragment).navigateUp() }
هذا هو كل شيء ، الآن يمكنك تشغيل التطبيق ومعرفة كيفية عمله.
ملخص
عمل جيد! لقد صنعنا نظام ملاحة:
- تقطع الاتصالات بين الأجزاء في وحدات الميزات المختلفة ،
- يمكن تغييرها بسهولة ،
- من السهل اختباره.
Crosspost