Úlohy pre 5. cvičenie

  • 1. Použitie ViewModelu a LiveData pre asynchrónne úlohy a uchovávanie údajov.
  • 2. Kotlin Coroutines.
  • 3. Komunikácia s REST webservisom pomocou Retrofitu a Gson na parsovanie JSONU.

Ukazkovy kod mozete najst na https://github.com/marosc/mobv24


1. Použitie ViewModelu a LiveData pre asynchrónne úlohy a uchovávanie údajov.

Pridanie závislostí do projektu

ViewModel je súčasťou knižnice architektúry Android Jetpack a je navrhnutý tak, aby uchovával a spravoval dáta súvisiace s UI (užívateľským rozhraním) nezávisle od životného cyklu aktivít a fragmentov. To znamená, že dáta uchované v ViewModel zostanú nezmenené pri zmene konfigurácie, ako je napríklad otočenie obrazovky.

  • Oddelenie logiky: ViewModel pomáha oddeliť logiku pre prácu s dátami od UI, čím uľahčuje testovanie a udržiavanie kódu.
  • Uchovávanie dát: Umožňuje uchovávať dáta, ktoré sú nezávislé od zmien životného cyklu komponentov UI, ako sú aktivita alebo fragment.
  • Spolupráca s LiveData: ViewModel často pracuje s LiveData, čo je iná súčasť Jetpacku. LiveData je pozorovateľný dátový držiak, ktorý je vedomý životného cyklu komponentov. To znamená, že môže automaticky aktualizovať dáta v UI v pravý čas.

Na používanie LiveData a ViewModel v projekte je potrebné pridať nasledujúce závislosti do súboru `build.gradle`:

dependencies {
    // ViewModel a LiveData
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.livedata.ktx)

}
        
1. Nastavenie LiveData s reťazcom (String) a jeho použitie vo Fragmente

Na začiatok si ukážeme, ako nastaviť LiveData s jednoduchým reťazcom (String) a ako túto hodnotu pozorovať a reagovať na jej zmeny vo Fragment.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class FeedViewModel : ViewModel() {
    private val _sampleString = MutableLiveData<String>()
    val sampleString: LiveData<String> get() = _sampleString

    fun updateString(value: String) {
        _sampleString.value = value
    }
}

V tomto príklade je LiveData `_sampleString` privátne, a je z neho vystavené iba čítanie cez `sampleString`. Metóda `updateString` umožňuje aktualizovať hodnotu LiveData.

Rozdiel medzi `.value` a `.postValue()`:

  • .value: Používa sa na nastavenie hodnoty LiveData z hlavného vlákna. Pri pokuse o nastavenie hodnoty z vedľajšieho vlákna môže spôsobiť chybu. Keď je hodnota nastavená pomocou `.value`, informuje všetkých aktívnych pozorovateľov o zmene hodnoty.
  • .postValue(): Používa sa na nastavenie hodnoty LiveData z vedľajšieho vlákna. Aktualizuje hodnotu asynchrónne a informuje všetkých aktívnych pozorovateľov o zmene, keď je to vykonané na hlavnom vlákne.

ViewModel sa nevytvára priamo v aktivite alebo vo fragmente. Namiesto toho sa používa ViewModelProvider, ktorý zabezpečí, že ViewModel nie je zbytočne re-created pri každej zmene konfigurácie a môže sa používať na zdieľanie dát medzi rôznymi fragmentmi a aktivitami.

Ak chcete túto hodnotu pozorovať vo Fragment, môžete to urobiť nasledovne:

class FeedFragment : Fragment(R.layout.fragment_feed) {
    private lateinit var viewModel: FeedViewModel

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

        viewModel = ViewModelProvider(this)[FeedViewModel::class.java]
        // Pozorovanie zmeny hodnoty
        viewModel.sampleString.observe(viewLifecycleOwner, Observer { stringValue ->
            // Tu môžete aktualizovať UI podľa hodnoty stringValue
            Log.d("FeedFragment", "novy text: $stringValue")
        })

        viewModel.updateString("zmena textu")
    }
}

V príklade vyššie inicializujeme `viewModel` vo Fragment. Používame metódu `observe` na `sampleString`, aby sme mohli sledovať zmeny v hodnote a aktualizovať UI (napr. TextView) podľa tejto hodnoty.

2. Nastavenie LiveData s zoznamom položiek

Nasledujúci príklad ukazuje, ako nastaviť LiveData s Listom položiek. Predpokladáme, že máme definovanú triedu `MyItem` v adapteri pre položky v RecyclerView.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class FeedViewModel : ViewModel() {
    private val _feed_items = MutableLiveData<List<MyItem>>()
    val feed_items: LiveData<List<MyItem>> get() = _feed_items

    fun updateItems(items: List<MyItem>) {
        _feed_items.value = items
    }
}

Podobne ako v prvom príklade, máme privátnu MutableLiveData `_itemList` a vystavenú LiveData `itemList` na čítanie. Metóda `updateItemList` slúži na aktualizáciu zoznamu položiek.

3. Pozorovanie zmeny zoznamu a aktualizácia dát v RecyclerView

Ak chcete reagovať na zmeny v LiveData a aktualizovať RecyclerView, môžete to dosiahnuť takto:

class FeedFragment : Fragment(R.layout.fragment_feed) {
    private lateinit var viewModel: FeedViewModel

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

        view.findViewById<BottomBar>(R.id.bottom_bar).setActive(BottomBar.FEED)

        // Inicializácia ViewModel
        viewModel = ViewModelProvider(this)[FeedViewModel::class.java]

        val recyclerView = view.findViewById<RecyclerView>(R.id.feed_recyclerview)
        recyclerView.layoutManager = LinearLayoutManager(context)
        val feedAdapter = FeedAdapter()
        recyclerView.adapter = feedAdapter

        // Pozorovanie zmeny hodnoty
        viewModel.feed_items.observe(viewLifecycleOwner) { items ->
            // Tu môžete aktualizovať UI podľa hodnoty stringValue
            feedAdapter.updateItems(items)
        }

        viewModel.updateItems(
            listOf(
                MyItem(0, R.drawable.baseline_feed_24, "Prvy"),
                MyItem(1, R.drawable.baseline_map_24, "Druhy"),
                MyItem(2, R.drawable.baseline_account_box_24, "Treti"),
            )
        )

    }
}

V tomto príklade inicializujeme `viewModel` a `adapter` pre RecyclerView. Používame metódu `observe` na `itemList` z ViewModel, aby sme sledovali zmeny v zozname položiek a potom aktualizovali RecyclerView pomocou `adapter.updateItems(items)`.

4. Zdieľanie ViewModel medzi fragmentmi

Jednou z výhod používania `ViewModel` je možnosť zdieľať jeho inštancie medzi viacerými fragmentmi v rámci jednej aktivity. Toto je obzvlášť užitočné, keď potrebujete zdieľať alebo komunikovať dáta medzi fragmentmi.

Postup, ako zdieľať `ViewModel` medzi fragmentmi:

  1. Vytvorte `ViewModel` triedu, ako obvykle.
  2. Pri vytváraní inštancie `ViewModel` v fragmente použite metódu `by activityViewModels()`, ktorá zabezpečí, že fragmenty získajú rovnakú inštanciu `ViewModel`.

Príklad:

class SharedViewModel : ViewModel() {
    val sharedData: MutableLiveData<String> = MutableLiveData()
}

class FirstFragment : Fragment(R.layout.fragment_first) {
    private val viewModel: SharedViewModel by activityViewModels()
    
    // ... zvyšok kódu ...
}

class SecondFragment : Fragment(R.layout.fragment_second) {
    private val viewModel: SharedViewModel by activityViewModels()
    
    // ... zvyšok kódu ...
}
        

Obe fragmenty teraz používajú tú istú inštanciu `SharedViewModel`, čo umožňuje jednoduché zdieľanie dát medzi nimi. Keď jedna z fragmentov zmení dáta v `SharedViewModel`, druhá ju môže okamžite sledovať a reagovať na túto zmenu.

5. Výhody použitia ViewModel s LiveData v porovnaní s ukladaním premenných vo Fragment

Použitie ViewModel spolu s LiveData prináša viacero výhod v porovnaní s tradičným prístupom ukladania dát priamo vo Fragment. Nasledujú niektoré z hlavných výhod:

  • Odolnosť voči zmenám konfigurácie: Keď sa zmení konfigurácia (napr. otočenie obrazovky), Fragment sa zničí a znovu vytvorí. S ViewModel, údaje zostávajú nezmenené a nestratia sa počas týchto zmien.
  • Oddelenie logiky: ViewModel poskytuje čisté oddelenie UI logiky a dát, čo robí kód organizovanejší a ľahšie testovateľný.
  • Automatická aktualizácia UI: LiveData informuje pozorovateľov o zmenách údajov automaticky. Nemusíte manuálne aktualizovať UI v prípade zmien v dátach.
  • Bezpečnosť: ViewModel umožňuje privátne ukladanie MutableLiveData a vystavuje iba nemeniteľné LiveData, čo zabezpečuje, že údaje nemôžu byť zmenené z iných častí aplikácie.
  • Zníženie rizika úniku pamäte: ViewModely sú navrhnuté tak, aby prežili zmeny životného cyklu, čo znižuje riziko úniku pamäte a zrýchľuje prácu s údajmi v rôznych častiach životného cyklu komponenty.

2. Kotlin Coroutines.

1. Základy Kotlin Coroutines

Kotlin Coroutines sú súčasťou knižnice Kotlin a ponúkajú jednoduchý a efektívny spôsob práce s asynchrónnym a neblokujúcim kódom v Kotlin. V praxi nám coroutines umožňujú písať asynchrónny kód, ktorý vyzerá ako synchrónny.

2. Vytvorenie CoroutineScope

Na používanie Coroutines v projekte je potrebné pridať nasledujúce závislosti do súboru `build.gradle`:

dependencies {
    implementation(libs.kotlinx.coroutines.android)

}
        

Coroutines potrebujú rozsah (scope) na spustenie. `CoroutineScope` definuje životný cyklus coroutine:

val job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
        

V tomto príklade sme vytvorili nový rozsah so `Dispatchers.Main` a `Job` ako kontextom.

3. Spustenie Coroutine

Po vytvorení rozsahu môžete spustiť coroutine pomocou `launch` alebo `async`:

scope.launch {
    // Toto je blok coroutine
}
        

Coroutine sa vykonáva asynchrónne vo vopred definovanom rozsahu.

4. Práca s Dispatchers

`Dispatchers` určujú, na ktorom vlákne alebo zásobníku sa má coroutine spustiť. Najčastejšie používané sú:

  • `Dispatchers.Main`: hlavné vlákno UI.
  • `Dispatchers.IO`: optimalizované pre I/O operácie (napr. čítanie súborov, databázy).
  • `Dispatchers.Default`: optimalizované pre výpočty.
5. Suspendovanie funkcií

Suspendovanie funkcií sú funkcie, ktoré môžu asynchrónne výkonať dlhotrvajúcu operáciu a suspendovať sa bez blokovania vlákna. Deklarujú sa pomocou kľúčového slova `suspend`:

suspend fun fetchData(): DataType {
    // ... kód ...
}
        

Tieto funkcie je možné volať iba z coroutine alebo z iných suspendovacích funkcií.

6. Zrušenie Coroutine

Ak potrebujete zrušiť spustenú coroutine, môžete použiť `cancel()` na príslušnom `Job` objekte:

job.cancel()
        
7. Coroutines v ViewModel

Základné informácie:

`ViewModel` je ideálny pre prácu s Coroutines v kontexte Android aplikácií. V kombinácii s Coroutines, ViewModel poskytuje silnú základňu pre spracovanie dát v UI vrstve Androidu, najmä pri práci s asynchrónnymi operáciami.

Použitie viewModelScope:

`viewModelScope` je rozšírenie pre `ViewModel`, ktoré poskytuje `CoroutineScope` spojený s životným cyklom `ViewModel`. Keď `ViewModel` je zlikvidovaný (napr. pri zničení aktivity alebo fragmentu), všetky coroutines v tomto rozsahu sú automaticky zrušené.

class SampleViewModel : ViewModel() {
    
    fun fetchData() {
        viewModelScope.launch {
            // Váš asynchrónny kód tu
        }
    }

    override fun onCleared() {
        super.onCleared()
        // Žiadne rušenie potrebné, viewModelScope sa automaticky zruší
    }
}
        

Odporúčania:

Pri práci s Coroutines vo `ViewModel` je odporúčané použiť `viewModelScope`. Týmto predíďte potenciálnym problémom s únikom pamäte a zabezpečíte správne spracovanie životného cyklu asynchrónnych operácií.

8. Príklad: Generovanie náhodných čísel s Coroutines

Suspendovanie funkcie:

suspend fun fetchRandomNumber(): Int {
    delay(5000)
    return (0..10).random()
}
        

ViewModel:

class NumberViewModel : ViewModel() {
    private val _randomNumber = MutableLiveData<Int>()
    val randomNumber: LiveData<Int> get() = _randomNumber

    fun generateRandomNumber() {
        viewModelScope.launch {
            val number = fetchRandomNumber()
            _randomNumber.postValue(number)
        }
    }
}
        

Fragment:

class NumberFragment : Fragment() {
    private lateinit var viewModel: NumberViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_number, container, false)
        viewModel = ViewModelProvider(this).get(NumberViewModel::class.java)

        viewModel.randomNumber.observe(viewLifecycleOwner, Observer { number ->
            view.findViewById<TextView>(R.id.textViewNumber).text = number.toString()
        })

        view.findViewById<Button>(R.id.buttonGenerate).setOnClickListener {
            viewModel.generateRandomNumber()
        }

        return view
    }
}
        

Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    android:gravity="center">

    <TextView
        android:id="@+id/textViewNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:text="0" />

    <Button
        android:id="@+id/buttonGenerate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Generate Random Number" />

</LinearLayout>
        

3. Komunikácia s REST webservisom pomocou Retrofitu a Gson na parsovanie JSONU.

1. Pridanie potrebných závislostí

Na začiatok pridajte potrebné závislosti pre Retrofit a Gson do vášho súboru build.gradle:

    implementation(libs.retrofit)
    implementation(libs.converter.gson)
    implementation(libs.gson)
        

Retrofit je knižnica, ktorá umožňuje ľahko komunikovať s webovými službami v Androide. Gson je moderná knižnica pre spracovanie JSON v Jave a Kotlin, ktorá je efektívna a ľahko sa používa spolu s Retrofit.

2. Definícia API rozhrania

Retrofit pracuje na princípe definovania rozhrania pre váš web servis:

interface ApiService {
    @Headers("x-apikey: ...")
    @POST("user/create.php")
    suspend fun registerUser(@Body userInfo: UserRegistration): Response<RegistrationResponse>
}

data class UserRegistration(val name: String, val email: String, val password: String)
data class RegistrationResponse(val uid: String, val access: String, val refresh: String)
        

V tomto rozhraní sú definované koncové body webovej služby. Anotácie, ako je @POST, označujú akú HTTP metódu má metóda rozhrania reprezentovať. Retrofit automaticky prevedie váš požiadavok a odpoveď na a z JSON pomocou konvertora, v tomto prípade Moshi.

Prehľad annotácií Retrofit

Retrofit poskytuje množstvo annotácií, ktoré vám umožňujú efektívne definovať a prispôsobiť sieťové požiadavky. Tu je zoznam niektorých z nich:

  • @GET: Označuje metódu, ktorá by mala interpretovať HTTP GET požiadavku.
  • @POST: Označuje metódu, ktorá by mala interpretovať HTTP POST požiadavku.
  • @PUT: Pre HTTP PUT požiadavku.
  • @DELETE: Pre HTTP DELETE požiadavku.
  • @PATCH: Pre HTTP PATCH požiadavku.
  • @HEAD: Pre HTTP HEAD požiadavku.
  • @Path: Používa sa na označenie parametrov v URL. Napr. "@GET("users/{id}") fun getUser(@Path("id") userId: Int): Call<User>"
  • @Query: Používa sa na označenie query parametrov v URL. Napr. "@GET("users") fun getUsers(@Query("sort") order: String): Call<List<User>>"
  • @Body: Používa sa na označenie tela požiadavky. Napr. "@POST("users") fun createUser(@Body user: User): Call<User>"
  • @Header: Označuje parameter metódy ako header požiadavky.
  • @Headers: Používa sa na preddefinované headery pre požiadavku.
  • @FormUrlEncoded: Označuje, že požiadavka bude kódovaná v tvare x-www-form-urlencoded.
  • @Field: Používa sa s @FormUrlEncoded na označenie jedného kľúča-páru hodnoty vo formulári.

Keď očakávate, že API nebude vracať telo odpovede, môžete použiť Response<Void> ako typ návratovej hodnoty metódy. Toto vám umožní získať informácie o odpovedi (napr. stavový kód), ale bez tela:

@POST("users/logout")
fun logout(): Call<Response<Void>>
        

V tomto prípade môžete kontrolovať, či bola požiadavka úspešná, avšak telo odpovede bude prázdne.

3. Vytvorenie Retrofit klienta ako singleton

Na komunikáciu s vaším API potrebujete vytvoriť inštanciu Retrofit:

Rozhranie ApiService je časť aplikácie, ktorá sa stará o komunikáciu s webovými službami. Používa knižnicu Retrofit na zjednodušenie HTTP operácií. V tomto kóde máme:

  • Metódu registerUser, ktorá odošle údaje používateľa na server. Používa anotácie Retrofitu na definovanie hlavičiek a typu požiadavky.
  • companion object, ktorý obsahuje metódu create pre vytvorenie inštancie Retrofitu. Táto inštancia je nakonfigurovaná s základným URL a konvertorom Gson pre prácu s JSON dátami.

Keď aplikácia volá registerUser, Retrofit dynamicky vytvorí HTTP požiadavku, odošle ju na server a čaká na odpoveď, všetko zvládne asynchrónne vďaka integrácii s Kotlin korutínami.

interface ApiService {
    @Headers("x-apikey: ...")
    @POST("user/create.php")
    suspend fun registerUser(@Body userInfo: UserRegistration): Response<RegistrationResponse>

    companion object{
        fun create(): ApiService {

            val retrofit = Retrofit.Builder()
                .baseUrl("https://zadanie.mpage.sk/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()

            return retrofit.create(ApiService::class.java)
        }
    }
}
        

Využívaním objektu `ApiService.create()`, môžete získať prístup k `apiService`.

4. Jednotny zdroj dat v aplikácii.

Trieda DataRepository je zodpovedná za správu prístupu k dátam v aplikácii. Táto implementácia používa návrhový vzor Singleton, aby sa zaistilo, že existuje iba jedna inštancia triedy v celej aplikácii. Kľúčové body v kóde zahŕňajú:

  • private constructor: Zabraňuje priamej inštanciácii triedy zvonku.
  • companion object: Obsahuje logiku pre vytvorenie a získanie jedinečnej inštancie triedy.
  • @Volatile a synchronized: Tieto kľúčové slová sú použité na zabezpečenie, že operácia vytvorenia inštancie je bezpečná vo viacvláknovom prostredí.

getInstance(): Táto metóda overí, či už inštancia existuje; ak nie, vytvorí novú inštanciu DataRepository a táto nová inštancia sa uloží do INSTANCE.

Pre jednotny zdroj data v aplikácii budeme pouzivat DataRepository navrhovy vzor. Teda trieda, ktora bude implementovat logiku ci sa udaje ziskavaju z databazy alebo REST webservisu. A tiez bude obsahovat logiku ukladania ziskanich informacii z webservisu do lokalnej databazy.

class DataRepository private constructor(
    private val service: ApiService
) {
    companion object {
        const val TAG = "DataRepository"

        @Volatile
        private var INSTANCE: DataRepository? = null
        private val lock = Any()

        fun getInstance(): DataRepository =
            INSTANCE ?: synchronized(lock) {
                INSTANCE
                    ?: DataRepository(ApiService.create()).also { INSTANCE = it }
            }
    }
    
}
        

Využívaním objektu `DataRepository.getInstance()`, môžete získať prístup k DataRepository.

5. Registracia pouzivatela.

Ak chceme urobit prvu poziadavku na REST API pre registraciu pouzivatela mozeme pridat nasledovny kod do kodu DataRepository:


suspend fun apiRegisterUser(username: String, email: String, password: String) : Pair<String,User?>{
        if (username.isEmpty()){
            return Pair("Username can not be empty", null)
        }
        if (email.isEmpty()){
            return Pair("Email can not be empty", null)
        }
        if (password.isEmpty()){
            return Pair("Password can not be empty", null)
        }
        try {
            val response = service.registerUser(UserRegistration(username, email, password))
            if (response.isSuccessful) {
                response.body()?.let { json_response ->
                    return Pair("", User(username,email,json_response.uid, json_response.access, json_response.refresh))
                }
            }
            return Pair("Failed to create user", null)
        }catch (ex: IOException) {
            ex.printStackTrace()
            return Pair("Check internet connection. Failed to create user.", null)
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
        return Pair("Fatal error. Failed to create user.", null)
}
        

Funkcia apiRegisterUser vykonáva registráciu používateľa prostredníctvom API. Vykonáva kontrolu vstupných údajov a spracováva odpoveď zo servera. Tu je stručný opis toho, ako funguje:

  • Kontroluje, či sú reťazce username, email, a password neprázdne. Vracia chybové správy, ak je niektorý z nich prázdny.
  • Používa metódu registerUser zo služby ApiService na poslanie požiadavky na registráciu používateľa.
  • Spracováva odpoveď zo servera. Ak je odpoveď úspešná a obsahuje telo, vytvorí nového používateľa s údajmi z odpovede a vráti ho.
  • V prípade chyby počas komunikácie s API alebo inej výnimky vráti chybovú správu a null pre používateľa.

Funkcia efektívne rieši rôzne scenáre, ktoré môžu nastať pri komunikácii so serverom, a zabezpečuje, aby boli chyby adekvátne ošetrené a komunikované späť volajúcej strane.

6. ViewModel implementácia s apiService ako argumentom

Použite Retrofit klienta v ViewModeli pre spracovanie požiadaviek:

class AuthViewModel(private val dataRepository: DataRepository) : ViewModel() {
    private val _registrationResult = MutableLiveData<Pair<String,User?>>()
    val registrationResult: LiveData<Pair<String,User?>> get() = _registrationResult

    fun registerUser(username: String, email: String, password: String) {
        viewModelScope.launch {
            _registrationResult.postValue(dataRepository.apiRegisterUser(username, email, password))            
        }
    }
}
        

ViewModel teraz prijíma DataRepository ako argument, čo znamená, že ho môžete poskytnúť pri vytváraní inštancie ViewModelu. To umožňuje väčšiu pružnosť a lepšie testovateľnosť.

5. Observácia LiveData vo Fragmente

RegistrationFragment je súčasťou užívateľského rozhrania, ktorá zodpovedá za registráciu nových používateľov. Tento fragment využíva ViewModel pre správu a uchovanie stavu registrácie. Nasleduje podrobný popis jeho funkcionality:

  • Fragment sa spolieha na RegistrationViewModel pre správu dát a logiky registrácie.
  • V metóde onViewCreated sa inicializuje viewModel pomocou ViewModelProvider, ktorý vytvorí inštanciu AuthViewModel s potrebným DataRepository.
  • Fragment sleduje registrationResult v viewModel pomocou metódy observe. V prípade úspešnej registrácie naviguje na ďalší fragment pomocou findNavController().navigate. Ak registrácia zlyhá, zobrazí chybovú správu pomocou Snackbar.
  • Kliknutím na tlačidlo "Submit" sa spustí metóda registerUser z viewModel, ktorá odošle zadané údaje (meno používateľa, email, heslo) do metódy registerUser v AuthViewModel.

Tento kód efektívne oddeluje logiku užívateľského rozhrania od spracovania dát a biznis logiky, čím zjednodušuje údržbu a testovanie.

class RegistrationFragment : Fragment() {

    private lateinit var viewModel: RegistrationViewModel


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

        viewModel = ViewModelProvider(requireActivity(), object : ViewModelProvider.Factory {
            override fun <T : ViewModel< create(modelClass: Class<T<): T {
                return AuthViewModel(DataRepository.getInstance()) as T
            }
        })[AuthViewModel::class.java]

        viewModel.registrationResult.observe(viewLifecycleOwner){
            if (it.second != null){
                requireView().findNavController().navigate(R.id.action_signup_feed)
            }else{
                Snackbar.make(
                    view.findViewById(R.id.submit_button),
                    it.first,
                    Snackbar.LENGTH_SHORT
                ).show()
            }
        }

        view.findViewById<TextView<(R.id.submit_button).apply {
            setOnClickListener {
                viewModel.registerUser(
                    view.findViewById<EditText<(R.id.edit_text_username).text.toString(),
                    view.findViewById<EditText<(R.id.edit_text_email).text.toString(),
                    view.findViewById<EditText<(R.id.edit_text_email).text.toString()
                )
            }
        }
    }
}
        
6. Povolenie pre pripojenie sa na internet

Uz pravdepodobne mate pridane povolenie na komunikaciu cez internet, kvoli mape, ktoru ste pridavali.

<manifest ... >		
<uses-permission android:name="android.permission.INTERNET" />
<application
...
</manifest>
        

Treba mat v AndroidManifest.xml.

5. Prihlasenie sa do uctu s REST webservisom pomocou Retrofitu a Gson na parsovanie JSONU.

1. Prihlasenie sa so svojim uctom

Pouzite rovnaky sposob ako pri registracii na prihlasenie sa do uctu cez REST API.