המלצות לארכיטקטורה של Android

בדף הזה מוצגות מספר שיטות מומלצות והמלצות שקשורות לארכיטקטורה. כדאי להשתמש בהם כדי לשפר את האיכות, התקינות והמדרגיות של האפליקציה. הן גם שיעזרו לתחזק ולבדוק את האפליקציה.

השיטות המומלצות שמפורטות בהמשך מקובצות לפי נושאים. לכל אחת מהן יש עדיפות שמשקפת עד כמה הצוות ממליץ על כך. בהמשך מופיעה רשימת העדיפויות:

  • מומלץ מאוד: כדאי ליישם את השיטה הזו אלא אם יש התנגשות כזו מהיסוד בגישה שלכם.
  • מומלץ: השיטה הזו תשפר את האפליקציה.
  • אופציונלי: השיטה הזו יכולה לשפר את האפליקציה בנסיבות מסוימות.

ארכיטקטורה בשכבות

בעזרת הארכיטקטורה השכבות המומלצת שלנו, יש הפרדה בין בעיות. הוא שמפנה את ממשק המשתמש ממודלים של נתונים, תואם לעקרון המקור היחיד של האמת, ופועלת בהתאם לעקרונות של זרימת נתונים חד-כיוונית. הנה כמה טובים יותר לארכיטקטורה רב-שכבתית:

המלצה תיאור
צריך להשתמש בשכבת נתונים שמוגדרת בבירור.
מומלץ מאוד
שכבת הנתונים חושפת את נתוני האפליקציה לשאר האפליקציה ומכילה את הרוב המכריע של הלוגיקה העסקית שלה.
  • צריך ליצור מאגרים גם אם הם מכילים רק מקור נתונים אחד.
  • באפליקציות קטנות, אפשר למקם סוגים של שכבות נתונים בחבילה או במודול של data.
השתמשו בשכבת ממשק משתמש שמוגדרת בבירור.
מומלץ מאוד
שכבת ממשק המשתמש מציגה את נתוני האפליקציה במסך ומשמשת כנקודה הראשית לאינטראקציה של המשתמש.
  • באפליקציות קטנות, אפשר למקם סוגים של שכבות נתונים בחבילה או במודול של ui.
כאן אפשר למצוא עוד שיטות מומלצות ליצירת שכבת ממשק משתמש.
שכבת הנתונים צריכה לחשוף את נתוני האפליקציה באמצעות מאגר.
מומלץ מאוד

לרכיבים בשכבת ממשק המשתמש, כמו תכנים קומפוזביליים, פעילויות או ViewModels, אסור שתהיה אינטראקציה ישירה עם מקור נתונים. דוגמאות למקורות נתונים:

  • מסדי נתונים, DataStore, SharedPreferences ו-Firebase APIs.
  • ספקי מיקום לפי GPS.
  • ספקי נתונים של Bluetooth.
  • ספק סטטוס הקישוריות לרשת.
להשתמש ב-coroutines ו-flows.
מומלץ מאוד
להשתמש בקורוטין וזרימה כדי לתקשר בין שכבות.

כאן מפורטות שיטות מומלצות נוספות לגבי קורוטינים.

משתמשים בשכבת דומיין.
המלצות באפליקציות גדולות
השתמשו בשכבת דומיין, אם אתם צריכים לעשות שימוש חוזר בלוגיקה עסקית שמקיימת אינטראקציה עם שכבת הנתונים במספר מודלים של ViewModels, או אם אתם רוצים לפשט את המורכבות של הלוגיקה העסקית של תצוגה מפורטת ספציפית

שכבה בממשק המשתמש

התפקיד של שכבת ממשק המשתמש הוא להציג את נתוני האפליקציה במסך. והם משמשים כנקודה העיקרית לאינטראקציה של המשתמש. ריכזנו כאן כמה שיטות מומלצות לשכבת ממשק המשתמש:

המלצה תיאור
פועלים בהתאם לזרימת נתונים חד-כיוונית (UDF).
מומלץ מאוד
לפעול לפי העקרונות של זרימת נתונים חד-כיווניים (UDF), שבהם מודלים של ViewModels חושפים את מצב ממשק המשתמש באמצעות דפוס הצופה ומקבלים פעולות מממשק המשתמש באמצעות קריאות ל-method.
השתמשו ב-AAC ViewModels אם היתרונות שלהם רלוונטיים לאפליקציה שלכם.
מומלץ מאוד
להשתמש ב-AAC ViewModels כדי לטפל בלוגיקה עסקית, ולאחזר נתוני אפליקציה כדי לחשוף את מצב ממשק המשתמש לממשק המשתמש (כתיבה או תצוגות של Android).

כאן אפשר למצוא עוד שיטות מומלצות ליצירת מודלים.

כאן תוכלו לקרוא על היתרונות של ViewModels.

שימוש באוסף המצבים של ממשק המשתמש תוך התייחסות למחזור החיים.
מומלץ מאוד
איסוף המצב של ממשק המשתמש מממשק המשתמש באמצעות הכלי המתאים ליצירת קורוטין שמתחשב למחזור החיים: repeatOnLifecycle במערכת התצוגה המפורטת ו-collectAsStateWithLifecycle ב-Jetpack פיתוח נייטיב.

מידע נוסף על repeatOnLifecycle.

מידע נוסף על collectAsStateWithLifecycle.

אין לשלוח אירועים מ-ViewModel לממשק המשתמש.
מומלץ מאוד
מעבדים את האירוע באופן מיידי ב-ViewModel וגורמים לעדכון מצב עם תוצאת הטיפול באירוע. מידע נוסף על אירועים בממשק המשתמש
להשתמש באפליקציה של פעילות יחידה.
המלצות
אם לאפליקציה יש יותר ממסך אחד, אפשר להשתמש בקטעי ניווט או בכתיבת ניווט כדי לנווט בין מסכים ולקשר עומק לאפליקציה.
משתמשים ב-Jetpack פיתוח נייטיב.
המלצות
אפשר להשתמש ב-Jetpack Compose כדי ליצור אפליקציות חדשות לטלפונים, לטאבלטים, למכשירים מתקפלים ול-Wear OS.

קטע הקוד הבא מסביר איך לאסוף את המצב של ממשק המשתמש תוך התייחסות למחזור החיים אופן:

צפיות

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

פיתוח נייטיב

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

לצפייה במודל

ViewModels אחראים לספק את המצב של ממשק המשתמש ולגשת אל בשכבת הנתונים. ריכזנו כאן כמה שיטות מומלצות לשימוש ב-ViewModels:

המלצה תיאור
המודלים של ViewModel צריכים להיות נפרדים ממחזור החיים של Android.
מומלץ מאוד
אסור לכלול ב-ViewModels הפניה לאף סוג שקשור למחזור החיים. אין להעביר את Activity, Fragment, Context או את Resources כתלות. אם משהו זקוק ל-Context ב-ViewModel, כדאי להעריך היטב אם הוא נמצא בשכבה הנכונה.
להשתמש ב-coroutines ו-flows.
מומלץ מאוד

ה-ViewModel מקיים אינטראקציה עם הנתונים או עם שכבות הדומיין באמצעות:

  • של Kotlin לקבלת נתוני אפליקציה,
  • suspend פונקציות לביצוע פעולות באמצעות viewModelScope.
שימוש ב-ViewModels ברמת המסך.
מומלץ מאוד

אין להשתמש ב-ViewModels בחלקים לשימוש חוזר בממשק משתמש. צריך להשתמש ב-ViewModels במדינות הבאות:

  • תכנים קומפוזביליים ברמת המסך,
  • פעילויות/קטעים בתצוגות,
  • יעדים או גרפים כשמשתמשים בניווט ב-Jetpack.
להשתמש בסיווגים מחזיקים במצב פשוט ברכיבי ממשק משתמש לשימוש חוזר.
מומלץ מאוד
להשתמש בסיווגים מחזיקים במצב פשוט לטיפול במורכבות ברכיבי ממשק משתמש לשימוש חוזר. כך ניתן להעלות את המדינה ולשלוט בה באופן חיצוני.
אין להשתמש ב-AndroidViewModel.
המלצות
משתמשים בכיתה ViewModel ולא ב-AndroidViewModel. אין להשתמש במחלקה Application ב-ViewModel. במקום זאת, צריך להעביר את התלות לממשק המשתמש או לשכבת הנתונים.
חשיפת מצב של ממשק משתמש
המלצות
מודלים של תצוגה צריכים לחשוף נתונים לממשק המשתמש באמצעות נכס יחיד שנקרא uiState. אם בממשק המשתמש מוצגים כמה קטעי נתונים לא קשורים, המכונה הווירטואלית יכולה לחשוף מספר מאפיינים של מצב ממשק המשתמש.
  • צריך להגדיר את uiState כ-StateFlow.
  • צריך ליצור את uiState באמצעות האופרטור stateIn עם המדיניות WhileSubscribed(5000) (דוגמה) אם הנתונים מגיעים כמקור נתונים משכבות אחרות בהיררכיה.
  • במקרים פשוטים יותר שבהם לא מגיעים מקורות נתונים משכבת הנתונים, ניתן להשתמש ב-MutableStateFlow שחשוף בתור StateFlow (דוגמה) שלא ניתן לשינוי.
  • אפשר להגדיר את ${Screen}UiState כסיווג נתונים שיכול להכיל נתונים, שגיאות ואותות טעינה. הסיווג הזה יכול להיות גם מחלקה חתומה אם המדינות השונות אינן בלעדיות.

קטע הקוד הבא מתאר איך לחשוף את מצב ממשק המשתמש מ-ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

מחזור חיים

הנה כמה שיטות מומלצות לעבודה עם Android מחזור חיים:

המלצה תיאור
אין לשנות שיטות של מחזור החיים בפעילויות או במקטעים.
מומלץ מאוד
אין לשנות שיטות של מחזור החיים, כמו onResume בפעילויות או במקטעים. במקום זאת, אתם צריכים להשתמש ב-LifecycleObserver. אם האפליקציה צריכה לפעול כשמחזור החיים מגיע ל-Lifecycle.State מסוים, אפשר להשתמש ב-API של repeatOnLifecycle.

קטע הקוד הבא מתאר איך לבצע פעולות בהינתן נתון מצב מחזור החיים:

צפיות

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

פיתוח נייטיב

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

טיפול ביחסי תלות

יש כמה שיטות מומלצות שכדאי ליישם כשמנהלים את יחסי התלות בין הרכיבים:

המלצה תיאור
להשתמש בהחדרת תלות.
מומלץ מאוד
כדאי להשתמש בשיטות המומלצות של החדרת תלות, בעיקר החדרת תלות כשהדבר אפשרי.
היקף ההרשאות לרכיב, במקרה הצורך.
מומלץ מאוד
היקף ההרשאות למאגר תגים של תלות במקרים שבהם הסוג מכיל נתונים שניתנים לשינוי ושצריך לשתף אותם, או כשהאתחול של הסוג יקר, והוא נמצא בשימוש נרחב באפליקציה.
משתמשים בערך Hilt.
המלצות
באפליקציות פשוטות ניתן להשתמש בהטלה או בהחדרת תלות ידנית. אם הפרויקט מורכב מספיק, משתמשים באפשרות Hilt. לדוגמה, אם יש לך:
  • מספר מסכים עם ViewModels – שילוב
  • שימוש ב-WorkManager – שילוב
  • שימוש מתקדם בניווט, כגון ViewModels בהיקף של תרשים הניווט — שילוב.

בדיקה

ריכזנו כאן כמה שיטות מומלצות לבדיקה:

המלצה תיאור
חשוב לדעת מה לבדוק.
מומלץ מאוד

אלא אם הפרויקט פשוט כמו אפליקציית hello World, כדאי לבדוק אותו, לפחות באמצעות:

  • מודלים של תצוגה מפורטת לבדיקת יחידה, כולל Flows.
  • ישויות של שכבות נתונים של בדיקת יחידה. כלומר מאגרים ומקורות נתונים.
  • בדיקות ניווט בממשק המשתמש שמועילות כבדיקות רגרסיה ב-CI.
עדיף להשתמש בזיופים על פני חיקויים.
מומלץ מאוד
מידע נוסף זמין במאמר שימוש במכפילים של בדיקה במסמכי התיעוד של Android.
בדיקת StateFlows.
מומלץ מאוד
כשבודקים את StateFlow:

מידע נוסף זמין במדריך מה צריך לבדוק ב-Android DAC.

דגמים

כשמפתחים מודלים באפליקציות, כדאי לפעול לפי השיטות המומלצות הבאות:

המלצה תיאור
יצירת מודל לכל שכבה באפליקציות מורכבות.
המלצות

באפליקציות מורכבות, כדאי ליצור מודלים חדשים בשכבות או ברכיבים שונים, לפי הצורך. מומלץ להביא בחשבון את הדוגמאות הבאות:

  • מקור נתונים מרוחק יכול למפות את המודל שהוא מקבל דרך הרשת למחלקה פשוטה יותר, עם הנתונים שדרושים לאפליקציה בלבד
  • מאגרים יכולים למפות מודלים של DAO לקטגוריות נתונים פשוטות יותר, עם המידע שדרוש לשכבת ממשק המשתמש.
  • ViewModel יכול לכלול מודלים של שכבות נתונים ב-UiState מחלקות.

מוסכמות מתן שמות

כשאתם נותנים שם ל-codebase, חשוב לשים לב לשיטות המומלצות הבאות:

המלצה תיאור
שיטות למתן שמות.
אופציונלי
השיטות צריכות להיות ביטוי של פועל. לדוגמה: makePayment().
מאפיינים של מתן שמות.
אופציונלי
המאפיינים צריכים להיות ביטוי של שם עצם. לדוגמה: inProgressTopicSelection.
מתן שמות למקורות נתונים.
אופציונלי
כשכיתה חושפת זרם זרימה, LiveData או כל מקור אחר, המוסכמה למתן שמות היא get{model}Stream(). לדוגמה, getAuthorStream(): Flow<Author>. אם הפונקציה מחזירה רשימת מודלים, שם המודל צריך להיות בצורת רבים: getAuthorsStream(): Flow<List<Author>>
הטמעות של מתן שמות לממשקים.
אופציונלי
השמות להטמעות של הממשקים צריכים להיות בעלי משמעות. אם לא נמצא שם טוב יותר, צריך להוסיף את Default כתחילית. לדוגמה, עבור ממשק NewsRepository, יכול להיות OfflineFirstNewsRepository או InMemoryNewsRepository. אם לא מצאת שם טוב, אפשר להשתמש ב-DefaultNewsRepository. התחילית של הטמעות מזויפות צריכה להיות Fake, כמו ב-FakeAuthorsRepository.