Android LiveData auf Kotlin mit Retrofit und Coroutinen

In diesem Artikel wird die Verwendung von Android-Komponenten ViewModel, LifeCycle und LiveData beschrieben. Mit diesen Komponenten können Sie sich nicht um den Aktivitätslebenszyklus kümmern.

Ein Beispiel für die Verwendung moderner Coroutinen in Verbindung mit dem Retrofit-Repository wird ebenfalls betrachtet.

fun main(args: Array<String>): Unit = runBlocking { // Wait (suspend) for Result val result: Result<User> = api.getUser("username").awaitResult() // Check result type when (result) { //Successful HTTP result is Result.Ok -> saveToDb(result.value) // Any HTTP error is Result.Error -> log("HTTP error with code ${result.error.code()}", result.error) // Exception while request invocation is Result.Exception -> log("Something broken", e) } } 

Erweiterung der Coroutinen nachrüsten

Kotlin-Coroutinen-Nachrüstung
Erweiterung zur Nachrüstung auf Kotlin. Dies sind nur zwei Dateien. Ich habe sie gerade zum Projekt hinzugefügt. Sie können sie über Abhängigkeit in Gradle verbinden. Es gibt Anwendungsbeispiele für Github.
Wir verbinden auch Adapter addCallAdapterFactory (CoroutineCallAdapterFactory ()) .
ServerAPI und Repository befinden sich in derselben Datei

REST-API

REST-API-Implementierung auf Kotlin. Sie hat keine spezifischen Änderungen.

ServerAPI
 import android.arch.lifecycle.MutableLiveData import android.util.Log import com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.async import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Call import retrofit2.http.* import ru.gildor.coroutines.retrofit.Result import ru.gildor.coroutines.retrofit.awaitResult object ServerAPI { var API_BASE_URL: String = getNetworkHost(); var httpClient = OkHttpClient.Builder().addInterceptor( HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE }) var builder: Retrofit.Builder = Retrofit.Builder() .baseUrl(API_BASE_URL) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .addConverterFactory(GsonConverterFactory.create()) var retrofit = builder .client(httpClient.build()) .build() var netService = retrofit.create<NetService>( NetService::class.java!!) interface NetService { @GET("api/stores") fun getStoreAll(@Header("Authorization") bearer: String): Call<Array<Store>> } } 


Livedata

Betrachten Sie als nächstes das Repository. Dies ist der Hauptdienst zum Empfangen von LiveData. Wir initialisieren LiveData mit dem Ladezustand: Resource.loading (null) . Warten Sie als Nächstes auf das Ende der Anforderung awaitResult (). Dieser Aufruf sollte sich im Coroutin Async (UI) -Block befinden

Am Ende der Anfrage können wir das Ergebnis bearbeiten. Wenn alles in Ordnung ist, wird das Ergebnis in mutableLiveData.value = Resource.success (result.value) gespeichert. Der wichtige Punkt ist, dass eine Verknüpfung zur neuen Instanz bestehen muss, da sonst Beobachter LiveData nicht funktioniert. siehe: neue Ressource <> (ERFOLG, Daten, null);

Repository
  class Repository { fun getStores(token: String) : MutableLiveData<Resource<Array<Store>>>{ val mutableLiveData = MutableLiveData<Resource<Array<Store>>>() mutableLiveData.value = Resource.loading(null) val req = PostsAPI.netService.getStoreAll(token) try { async(UI) { val result = req.awaitResult() // Check result type when (result) { //Successful HTTP result is Result.Ok -> { mutableLiveData.value = Resource.success(result.value) } // Any HTTP error is Result.Error -> { mutableLiveData.value = Resource.error("Http Error!", null) } // Exception while request invocation is Result.Exception -> Log.d(TAG, result.exception.message) } } } catch (e: Exception) { Log.d(TAG, e.toString()) } return mutableLiveData } } 


Wrapper-Daten

Für die Fehlerbehandlung und die Statusübertragung an Fragment wird Wrapper - Resource <T> verwendet .

Es speichert drei Zustände:

 public enum Status { SUCCESS, ERROR, LOADING } 

Die Daten selbst:

 @Nullable public final T data; 

Ressource <T>
Eine generische Klasse, die Daten und den Status zum Laden dieser Daten enthält

 // A generic class that contains data and status about loading this data. public class Resource<T> { @NonNull public final Status status; @Nullable public final T data; @Nullable public final String message; private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) { this.status = status; this.data = data; this.message = message; } public static <T> Resource<T> success(@NonNull T data) { return new Resource<>(Status.SUCCESS, data, null); } public static <T> Resource<T> error(String msg, @Nullable T data) { return new Resource<>(Status.ERROR, data, msg); } public static <T> Resource<T> loading(@Nullable T data) { return new Resource<>(Status.LOADING, data, null); } public enum Status { SUCCESS, ERROR, LOADING } } 


ViewModel

StoresViewModel fordert Daten vom Repository an und speichert sie in der Speichervariablen

 val api = Repository() stores = api.getStores(token) 

ViewModel
 class StoresViewModel (context: Context, token: String) : ViewModel() { val stores: MutableLiveData<Resource<Array<Store>>> init { val api = Repository() stores = api.getStores(token) } } 


ViewModelProviders

Um Parameter an ViewModel zu übergeben, erweitern wir die Standard- ViewModelProvider
Um beispielsweise an LoginViewModel zu übergeben, benötigen Sie zwei Parameter (Login, Password). Um ein Token an StoresViewModel zu übertragen , wird eines (Token) verwendet

AppViewModelFactory
 class AppViewModelFactory(private val contect: Context, vararg params: Any) : ViewModelProvider.NewInstanceFactory() { private val mParams: Array<out Any> init { mParams = params } override fun <T : ViewModel> create(modelClass: Class<T>): T { return if (modelClass == LoginViewModel::class.java) { LoginViewModel(contect, mParams[0] as String, mParams[1] as String) as T } else if (modelClass == StoresViewModel::class.java) { StoresViewModel(contect, mParams[0] as String) as T } else { super.create(modelClass) } } } 


Fragment

StoresViewModel abrufen:

 viewModel = ViewModelProviders.of(this, AppViewModelFactory(requireActivity(), tokenHolder.token)).get(StoresViewModel::class.java) 

Verwenden von Observer Observer für Datenänderungen:

  // Observe data on the ViewModel, exposed as a LiveData viewModel.stores.observe(this, Observer<Resource<Array<Store>>> { storesResource -> 

Fragment
  override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.stores_fragment, container, false) val tokenHolder = TokenHolder(PreferenceManager.getDefaultSharedPreferences(requireActivity())) viewModel = ViewModelProviders.of(this, AppViewModelFactory(requireActivity(), tokenHolder.token)).get(StoresViewModel::class.java) recyclerView = view.findViewById<RecyclerView>(R.id.store_list).apply { setHasFixedSize(true) } return view } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) // Observe data on the ViewModel, exposed as a LiveData viewModel.stores.observe(this, Observer<Resource<Array<Store>>> { storesResource -> val stores = storesResource?.data stores?.let { viewAdapter = StoresAdapter(stores!!) recyclerView.adapter = viewAdapter } if (storesResource?.status == Resource.LOADING){ log("Loading...") } if (storesResource?.status == Resource.ERROR){ log("Error : " + storesResource?.message) } }) } 


PS

Um Token zu speichern und in der gesamten Anwendung zu verwenden, habe ich die Bibliothek / Erweiterung von Fabio Collini verwendet. Die Anwendung ist in seinem Artikel gut beschrieben. Der Link befindet sich auf einer Seite auf Github oder darunter in diesem Artikel.

Prefs-Delegierte von Fabio Collini

 class TokenHolder(prefs: SharedPreferences) { var token by prefs.string() private set var count by prefs.int() private set fun saveToken(newToken: String) { token = newToken count++ } } 

Gradle

  implementation 'android.arch.lifecycle:extensions:1.1.1' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.30.0" implementation "com.squareup.retrofit2:retrofit:2.4.0" implementation "com.squareup.retrofit2:converter-gson:2.4.0" implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0" // If you use Kotlin 1.2 or 1.3 // compile 'ru.gildor.coroutines:kotlin-coroutines-retrofit:0.13.0' // compile 'ru.gildor.coroutines:kotlin-coroutines-retrofit:0.13.0-eap13' 

Links

Alles in einem Beispiel.

Beispiele für Android-Architekturkomponenten

LiveData-Übersicht

Asynchroner Code mit Kotlin Coroutines

Multithreading und Kotlin

Source: https://habr.com/ru/post/de427475/


All Articles