Este artículo habla sobre el uso de
componentes de Android ViewModel, LifeCycle y LiveData. Estos componentes le permiten no preocuparse por el ciclo de vida de la Actividad.
También se considera un ejemplo del uso de
Coroutines modernas junto con el repositorio Retrofit.
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) } }
Extensión de corutinas adaptadaskotlin-coroutines-retrofitExtensión para la modificación en Kotlin. Estos son solo dos archivos. Acabo de agregarlos al proyecto. Puede conectarlos a través de la dependencia en Gradle. Hay ejemplos de uso en Github.
También conectamos el adaptador
addCallAdapterFactory (CoroutineCallAdapterFactory ()) .
ServerAPI y Repository están en el mismo archivo
API RESTImplementación de API REST en Kotlin. Ella no tiene ningún cambio específico.
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>> } }
LivedataA continuación, considere el repositorio. Este es el servicio principal para recibir LiveData. Inicializamos LiveData con el estado de carga:
Resource.loading (nulo) . Luego, espere hasta el final de la solicitud
awaitResult () Esta llamada debe estar en el bloque Coroutin
async (UI)Al final de la solicitud, podemos manejar el resultado. Si todo está bien, el resultado se almacenará en
mutableLiveData.value = Resource.success (result.value). El punto importante es que debe haber un enlace a la nueva instancia; de lo contrario, el observador LiveData no funcionará. ver:
nuevo Recurso <> (ÉXITO, datos, nulo);Repositorio 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 } }
Datos del contenedorPara el manejo de errores y la transferencia de estado a Fragment,
se utiliza Wrapper -
Resource <T> .
Almacena tres estados:
public enum Status { SUCCESS, ERROR, LOADING }
Los datos en sí:
@Nullable public final T data;
ViewModelStoresViewModel solicita datos del repositorio y los almacena en la variable de
tiendas 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) } }
ViewModelProvidersPara pasar parámetros a ViewModel, ampliamos los
ViewModelProviders estándar
Por ejemplo, para pasar a
LoginViewModel necesita dos parámetros (Inicio de sesión, Contraseña). Para transferir un token a
StoresViewModel , se
usa uno (Token)
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) } } }
FragmentoObteniendo StoresViewModel:
viewModel = ViewModelProviders.of(this, AppViewModelFactory(requireActivity(), tokenHolder.token)).get(StoresViewModel::class.java)
Uso de Observer Observer para cambios de datos:
// Observe data on the ViewModel, exposed as a LiveData viewModel.stores.observe(this, Observer<Resource<Array<Store>>> { storesResource ->
Fragmento 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
Para almacenar Token y usarlo en toda la aplicación, utilicé la biblioteca / extensión de Fabio Collini. La aplicación está bien descrita en su artículo. El enlace está en una página en Github o debajo de este artículo.
delegados de prefs por 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'
EnlacesTodo en un ejemplo.Ejemplos de componentes de arquitectura de AndroidDescripción general de LiveDataCódigo asíncrono usando Kotlin CoroutinesMultithreading y kotlin