Este artigo fala sobre o uso de
componentes Android ViewModel, LifeCycle e LiveData. Esses componentes permitem que você não se importe com o ciclo de vida da atividade.
Um exemplo do uso de
Coroutines modernas em conjunto com o repositório Retrofit também é considerado.
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) } }
Extensão de corotinas retrofitkotlin-coroutines-retrofitExtensão para Retrofit no Kotlin. Estes são apenas dois arquivos. Acabei de adicioná-los ao projeto. Você pode conectá-los através da Dependency in Gradle. Existem exemplos de uso no Github.
Também conectamos o adaptador
addCallAdapterFactory (CoroutineCallAdapterFactory ()) .
ServerAPI e Repository estão no mesmo arquivo
API RESTImplementação da API REST no Kotlin. Ela não possui alterações específicas.
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>> } }
LivedataEm seguida, considere o repositório. Este é o principal serviço para receber o LiveData. Inicializamos o LiveData com o estado de carregamento:
Resource.loading (null) . Em seguida, aguarde o final da solicitação
waititResult () Essa chamada deve estar no bloco Coroutin
async (UI)No final da solicitação, podemos lidar com o resultado. Se tudo estiver bem, o resultado será armazenado em
mutableLiveData.value = Resource.success (result.value) O ponto importante é que deve haver um link para a nova instância, caso contrário, o observador LiveData não funcionará. veja:
novo Recurso <> (SUCESSO, dados, nulo);Repositório 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 } }
Dados do WrapperPara tratamento de erros e transferência de estado para o Fragmento, Wrapper -
Recurso <T> é usado .
Ele armazena três estados:
public enum Status { SUCCESS, ERROR, LOADING }
Os dados em si:
@Nullable public final T data;
ViewModelStoresViewModel solicita dados do repositório e os armazena na variável
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) } }
ViewModelProvidersPara passar parâmetros para o ViewModel, expandimos o padrão
ViewModelProvidersPor exemplo, para passar para o
LoginViewModel, você precisa de dois parâmetros (Login, Senha). Para transferir um token para
StoresViewModel , um (Token) é usado
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) } } }
FragmentoObtendo o StoresViewModel:
viewModel = ViewModelProviders.of(this, AppViewModelFactory(requireActivity(), tokenHolder.token)).get(StoresViewModel::class.java)
Usando o Observer Observer para alterações de dados:
// 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 armazenar o Token e usá-lo em todo o aplicativo, usei a biblioteca / extensão do Fabio Collini. A aplicação está bem descrita em seu artigo. O link está em uma página no Github ou abaixo neste artigo.
delegados 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'
LigaçõesTudo em um exemplo.Amostras de componentes da arquitetura do AndroidVisão geral do LiveDataCódigo assíncrono usando Kotlin CoroutinesMultithreading e kotlin