Cet article traite de l'utilisation
des composants Android ViewModel, LifeCycle et LiveData. Ces composants vous permettent de ne pas vous soucier du cycle de vie de l'activité.
Un exemple d'utilisation de
Coroutines modernes conjointement avec le référentiel Retrofit est également considéré.
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) } }
Rénovation de l'extension des coroutineskotlin-coroutines-retrofitExtension pour Retrofit sur Kotlin. Ce ne sont que deux fichiers. Je viens de les ajouter au projet. Vous pouvez les connecter via la dépendance dans Gradle. Il existe des exemples d'utilisation sur Github.
Nous connectons également l'adaptateur
addCallAdapterFactory (CoroutineCallAdapterFactory ()) .
ServerAPI et Repository sont dans le même fichier
API RESTImplémentation de l'API REST sur Kotlin. Elle n'a pas de changements spécifiques.
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>> } }
LivedataEnsuite, considérez le référentiel. Il s'agit du principal service de réception de LiveData. Nous initialisons LiveData avec l'état de chargement:
Resource.loading (null) . Ensuite, attendez la fin de la demande.
WaitResult () Cet appel doit être dans le bloc
asynchrone (UI) de Coroutin
.À la fin de la demande, nous pouvons gérer le résultat. Si tout va bien, le résultat sera stocké dans
mutableLiveData.value = Resource.success (result.value) Le point important est qu'il doit y avoir un lien vers la nouvelle instance, sinon l'observateur LiveData ne fonctionnera pas. voir:
nouvelle ressource <> (SUCCESS, data, null);Dépôt 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 } }
Données de wrapperPour la gestion des erreurs et le transfert d'état vers Fragment, Wrapper -
Resource <T> est utilisé .
Il stocke trois états:
public enum Status { SUCCESS, ERROR, LOADING }
Les données elles-mêmes:
@Nullable public final T data;
ViewModelStoresViewModel demande des données au référentiel et les stocke dans la variable
stores 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) } }
ViewModelProvidersPour passer des paramètres à ViewModel, nous développons les
ViewModelProviders standard
Par exemple, pour passer à
LoginViewModel, vous avez besoin de deux paramètres (Login, Password). Pour transférer un jeton vers
StoresViewModel , un (jeton) est utilisé
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) } } }
FragmentObtenir des magasinsViewModel:
viewModel = ViewModelProviders.of(this, AppViewModelFactory(requireActivity(), tokenHolder.token)).get(StoresViewModel::class.java)
Utilisation de l'Observer Observer pour les modifications de données:
// 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
Pour stocker Token et l'utiliser dans l'application, j'ai utilisé la bibliothèque / extension de Fabio Collini. L'application est bien décrite dans son article. Le lien se trouve sur une page sur Github ou ci-dessous dans cet article.
prefs-delegues par 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'
Les liensTout dans un exemple.Exemples de composants d'architecture AndroidPrésentation de LiveDataCode asynchrone à l'aide de Kotlin CoroutinesMultithreading et kotlin