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üstenKotlin-Coroutinen-NachrüstungErweiterung 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-APIREST-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>> } }
LivedataBetrachten 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-DatenFü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;
ViewModelStoresViewModel 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) } }
ViewModelProvidersUm Parameter an ViewModel zu übergeben, erweitern wir die Standard-
ViewModelProviderUm 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) } } }
FragmentStoresViewModel 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)
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'
LinksAlles in einem Beispiel.Beispiele für Android-ArchitekturkomponentenLiveData-ÜbersichtAsynchroner Code mit Kotlin CoroutinesMultithreading und Kotlin