Məzmuna keç
Educora
İrəli25 dəq13 / 14

Məlumat: şəbəkə, Room və DataStore

Retrofit və ya Ktor ilə serverdən JSON yüklə, sorğuları ViewModel-də korutinlərlə ekranı dondurmadan icra et, siyahıları Room verilənlər bazasında, parametrləri isə DataStore-da saxla.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Retrofit interfeysi ilə suspend şəbəkə sorğusu yazmaq və JSON-u data sinfə çevirmək
  • Sorğunu viewModelScope-da işə salıb yüklənmə, uğur və xəta vəziyyətlərini göstərmək
  • Room-da entity, DAO və verilənlər bazası sinfi yaratmaq
  • Kiçik parametrləri Preferences DataStore-da saxlamaq

Murad metroda Educora-nı açır, internet isə yoxdur. Yaxşı tətbiq bu anda boş ekran göstərmir: dərslər əvvəlcədən telefonda saxlanıb, tünd tema seçimi yadda qalıb, internet qayıdanda isə yeni dərslər serverdən sakitcə yüklənir. Bunun üçün üç alət lazımdır: şəbəkə üçün Retrofit və ya Ktor, siyahılar üçün Room verilənlər bazası, parametrlər üçün DataStore.

Şəbəkə: Retrofit və Ktor

Əvvəlcə AndroidManifest.xml-ə internet icazəsi əlavə et: <uses-permission android:name="android.permission.INTERNET" />. Retrofit Android-də ən geniş yayılmış HTTP kitabxanasıdır: sən sadəcə interfeys yazırsan, sorğunun kodunu isə kitabxana özü yaradır. JSON-u Kotlin obyektinə kotlinx.serialization çevirir. Aşağıdakı nümunə sınaq üçün açıq olan JSONPlaceholder xidmətindən tapşırıqlar siyahısını yükləyir.

Kotlin
@Serializable
data class Todo(val id: Int, val title: String, val completed: Boolean)

interface TodoApi {
    @GET("todos")
    suspend fun getTodos(): List<Todo>

    @GET("todos/{id}")
    suspend fun getTodo(@Path("id") id: Int): Todo
}

private val json = Json { ignoreUnknownKeys = true }

val todoApi: TodoApi = Retrofit.Builder()
    .baseUrl("https://jsonplaceholder.typicode.com/")
    .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
    .build()
    .create(TodoApi::class.java)
ignoreUnknownKeys = true JSON-dakı artıq sahələri (məsələn, userId) sakitcə buraxır. baseUrl mütləq / ilə bitməlidir.

Ktor JetBrains-in Kotlin üçün yaratdığı alternativdir; o, Kotlin Multiplatform-da da işləyir, yəni eyni şəbəkə kodu Android və iOS arasında bölüşdürülə bilər. Ktor-da sorğu belə görünür: client.get("https://jsonplaceholder.typicode.com/todos").body<List<Todo>>() — burada client ContentNegotiation plagini və json() ilə qurulmuş HttpClient-dir.

Kotlin
@Serializable
data class Lesson(val id: Int, val title: String, val minutes: Int)

fun main() {
    val lesson = Lesson(1, "Variables", 10)
    println(Json.encodeToString(lesson))

    val json = Json { ignoreUnknownKeys = true }
    val text = """{"id":2,"title":"Loops","minutes":15,"level":"beginner"}"""
    val loaded = json.decodeFromString<Lesson>(text)
    println(loaded)
    println("Total: ${lesson.minutes + loaded.minutes} min")
}
Gözlənilən nəticə
{"id":1,"title":"Variables","minutes":10}
Lesson(id=2, title=Loops, minutes=15)
Total: 25 min
Şəbəkəsiz yoxlama: kotlinx.serialization obyekti JSON-a və geri çevirir. level sahəsi sinifdə olmadığı üçün buraxılır.

Korutinlər ViewModel-də

Şəbəkə sorğusu saniyələr çəkə bilər, ona görə onu ViewModel-in **viewModelScope** sahəsində korutin kimi işə salırıq. İstifadəçi ekranı bağlayanda ViewModel silinir və bu sahədəki bütün korutinlər avtomatik ləğv olunur — lazımsız iş və yaddaş sızması olmur. Nəticəni isə keçən dərslərdən tanış olan sealed vəziyyətlə təsvir edirik.

Kotlin
sealed interface TodosUiState {
    data object Loading : TodosUiState
    data class Success(val todos: List<Todo>) : TodosUiState
    data class Error(val message: String) : TodosUiState
}

class TodosViewModel(private val api: TodoApi) : ViewModel() {
    private val _uiState = MutableStateFlow<TodosUiState>(TodosUiState.Loading)
    val uiState: StateFlow<TodosUiState> = _uiState.asStateFlow()

    init { load() }

    fun load() {
        viewModelScope.launch {
            _uiState.value = TodosUiState.Loading
            _uiState.value = try {
                TodosUiState.Success(api.getTodos())
            } catch (e: IOException) {
                TodosUiState.Error("No connection")
            } catch (e: HttpException) {
                TodosUiState.Error("Server error ${e.code()}")
            }
        }
    }
}
Ekran when (state) ilə üç halı göstərir: fırlanan indikator, siyahı və ya «Try again» düyməli xəta mesajı.
Kotlin
@Composable
fun TodosScreen(viewModel: TodosViewModel) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    when (val current = state) {
        TodosUiState.Loading -> CircularProgressIndicator()
        is TodosUiState.Success -> LazyColumn {
            items(current.todos, key = { it.id }) { todo -> Text(todo.title) }
        }
        is TodosUiState.Error -> Column {
            Text(current.message)
            Button(onClick = viewModel::load) { Text("Try again") }
        }
    }
}
Ekranda əvvəlcə fırlanan dairə, sonra tapşırıqların siyahısı; internet yoxdursa «No connection» mətni və «Try again» düyməsi.

Diqqət et: TodosViewModel konstruktorda api parametri alır, ona görə adi viewModel() onu yarada bilmir — ya kiçik fabrik (ViewModelProvider.Factory) yazılır, ya da real layihələrdə olduğu kimi Hilt və ya Koin kimi asılılıq inyeksiyası kitabxanası istifadə olunur. Adətən ViewModel ilə şəbəkə arasında bir qat da olur — repozitoriya (TodoRepository): o, məlumatın haradan gəldiyini (server, baza, keş) gizlədir, ViewModel isə yalnız onunla danışır.

Səhv: əsas axın bloklanır
fun load() {
    // blocks the main thread until the server answers
    val todos = runBlocking { api.getTodos() }
    _uiState.value = TodosUiState.Success(todos)
}
Düzgün: viewModelScope-da korutin
fun load() {
    viewModelScope.launch {
        _uiState.value = try {
            TodosUiState.Success(api.getTodos())
        } catch (e: IOException) {
            TodosUiState.Error("No connection")
        }
    }
}
Solda ekran cavab gələnə qədər donur, bir neçə saniyədən sonra sistem «Application Not Responding» (ANR) pəncərəsi göstərə bilər. Solda xəta da tutulmur.

Room: cihazdakı verilənlər bazası

Room SQLite üzərində qurulmuş rəsmi kitabxanadır. Üç hissədən ibarətdir: Entity — cədvəlin sətri olan data sinif; DAO — SQL sorğularını funksiyalar kimi təsvir edən interfeys; Database — onları birləşdirən abstrakt sinif. Kod KSP plagini ilə kompilyasiya zamanı yaradılır və SQL səhvləri elə o zaman tapılır.

Kotlin
@Entity(tableName = "lessons")
data class LessonEntity(
    @PrimaryKey val id: Int,
    val title: String,
    val done: Boolean = false
)

@Dao
interface LessonDao {
    @Query("SELECT * FROM lessons ORDER BY id")
    fun observeAll(): Flow<List<LessonEntity>>

    @Upsert
    suspend fun upsertAll(lessons: List<LessonEntity>)

    @Query("UPDATE lessons SET done = 1 WHERE id = :id")
    suspend fun markDone(id: Int)
}

@Database(entities = [LessonEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun lessonDao(): LessonDao
}
Verilənlər bazası bir dəfə yaradılır: Room.databaseBuilder(context, AppDatabase::class.java, "educora.db").build(). Flow qaytaran sorğu cədvəl hər dəfə dəyişəndə yeni siyahı göndərir.

DataStore: parametrlər üçün

Kotlin
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

private val DARK_THEME = booleanPreferencesKey("dark_theme")

class SettingsRepository(private val context: Context) {
    val darkTheme: Flow<Boolean> =
        context.dataStore.data.map { prefs -> prefs[DARK_THEME] ?: false }

    suspend fun setDarkTheme(enabled: Boolean) {
        context.dataStore.edit { prefs -> prefs[DARK_THEME] = enabled }
    }
}
preferencesDataStore faylın yuxarı səviyyəsində bir dəfə elan olunur; eyni fayl üçün ikinci nüsxə yaratmaq xətaya səbəb olur.
Nə saxlanırHarada
tema, dil, bildiriş açarlarıDataStore
dərslər, nəticələr, axtarış və çeşidləmə lazım olan siyahılarRoom
şəkillər və böyük fayllartətbiqin fayl qovluğu
hesab və bütün cihazlarda eyni olmalı olan irəliləyişserver
  1. 1
    App Inspection-u aç

    Tətbiqi emulyatorda işə sal və View › Tool Windows › App Inspection seç.

  2. 2
    Bazaya bax

    Database Inspector tabında educora.db və lessons cədvəlini aç. Cədvəl canlı yenilənir; SQL sorğusunu da orada yazıb yoxlaya bilərsən.

  3. 3
    Sorğuları izlə

    Network Inspector tabında hər HTTP sorğusunu, onun vaxtını və serverin qaytardığı JSON-u görəcəksən.

bütün layihədə mətn axtarmaq (Mac: Cmd+Shift+F)Ctrl+Shift+F
ifadəni tamamlamaq: mötərizə və fiqurlu mötərizələri özü bağlayır (Mac: Cmd+Shift+Return)Ctrl+Shift+Enter
sətri təkrarlamaq (Mac: Cmd+D)Ctrl+D
sətri şərhə çevirmək və ya geri qaytarmaq (Mac: Cmd+/)Ctrl+/

Əsas fikirlər

  • Şəbəkə üçün manifestdə INTERNET icazəsi lazımdır; Retrofit interfeysdəki suspend funksiyalardan sorğu kodu yaradır.
  • Sorğular viewModelScope.launch daxilində işləyir; ekran bağlananda onlar avtomatik ləğv olunur.
  • Nəticəni Loading / Success / Error sealed vəziyyəti ilə təsvir et və xətaları try/catch ilə tut.
  • Room: Entity + DAO + Database; Flow qaytaran sorğu cədvəl dəyişəndə ekranı özü yeniləyir.
  • Kiçik parametrlər DataStore-da saxlanır; ekran üçün vahid həqiqət mənbəyi yerli bazadır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Tətbiqin internetə çıxması üçün manifestdə hansı icazə olmalıdır?