Retrofit, Coroutines, ModelViewModel, Fragment, RecyclerView Usage In Kotlin
19-05-2020Fragment
import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.codesenior.period.tracker.R import com.codesenior.period.tracker.adapters.QuestionAdapter import com.codesenior.period.tracker.factories.QuestionViewModelFactory import com.codesenior.period.tracker.repositories.QuestionRepository import kotlinx.android.synthetic.main.question_fragment.* class QuestionFragment : Fragment() { companion object { fun newInstance() = QuestionFragment() } private lateinit var factory: QuestionViewModelFactory private lateinit var viewModel: QuestionViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.question_fragment, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) factory = QuestionViewModelFactory(repository = QuestionRepository()) viewModel = ViewModelProvider(this, factory).get(QuestionViewModel::class.java) viewModel.getQuestions() viewModel.questions.observe(viewLifecycleOwner, Observer { questions -> recycler_view_questions.also { it.layoutManager = LinearLayoutManager(requireContext()) it.setHasFixedSize(true) it.adapter = QuestionAdapter(questions) } }) } }
QuestionViewModelFactory
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.codesenior.period.tracker.repositories.QuestionRepository import com.codesenior.period.tracker.ui.fragments.QuestionViewModel @Suppress("UNCHECKED_CAST") class QuestionViewModelFactory(private val repository: QuestionRepository) : ViewModelProvider.NewInstanceFactory() { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return QuestionViewModel(repository) as T } }
QuestionViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.codesenior.period.tracker.coroutines.Coroutines import com.codesenior.period.tracker.models.Question import com.codesenior.period.tracker.repositories.QuestionRepository import kotlinx.coroutines.Job class QuestionViewModel(private val repository: QuestionRepository) : ViewModel() { private lateinit var job: Job private val _questions = MutableLiveData<List<Question>>() val questions: LiveData<List<Question>> get() = _questions fun getQuestions() { job = Coroutine.ioThenMain( { repository.getQuestionsAsync(1) }, { _questions.value = it?.data?.data }) } override fun onCleared() { super.onCleared() if (::job.isInitialized) job.cancel() } }
Coroutines
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch object Coroutine { fun <T : Any> ioThenMain(work: suspend (() -> T?), callback: ((T?) -> Unit)) = CoroutineScope(Dispatchers.Main).launch { val data = CoroutineScope(Dispatchers.IO).async rt@{ return@rt work() }.await() callback(data) } }
QuestionRepository
class QuestionRepository { private var questionProvider = QuestionProvider() suspend fun getQuestionsAsync(page: Int) = questionProvider.getQuestions(page) }
QuestionProvider
class QuestionProvider { suspend fun getQuestions(page: Int): PaginationResponse<Question> { val apiClient = ApiClient.getClient(Config.REST_API) val service = apiClient.create(QuestionService::class.java) return service.getQuestions(page) } }
ApiClient
import com.google.gson.FieldNamingPolicy import com.google.gson.GsonBuilder import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit object ApiClient { fun getClient(baseUrl: String?): Retrofit { return getClient(baseUrl, null) } fun getClient(baseUrl: String?, accessToken: String?): Retrofit { val interceptor = HttpLoggingInterceptor() interceptor.level = HttpLoggingInterceptor.Level.BODY val client = OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .addInterceptor(interceptor) .addInterceptor { chain -> val ongoing = chain.request().newBuilder() ongoing.addHeader("Accept", "application/json;versions=1") if (accessToken != null) { ongoing.addHeader("Authorization", "Bearer $accessToken") } chain.proceed(ongoing.build()) }.build() val gson = GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setDateFormat("yyyy-MM-dd HH:mm:ss") .create() return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create(gson)) .client(client) .build() } }
QuestionService
import com.codesenior.period.tracker.models.PaginationResponse import com.codesenior.period.tracker.models.Question import retrofit2.http.GET import retrofit2.http.Query interface QuestionService { @GET("questions") suspend fun getQuestions(@Query("page") page: Int): PaginationResponse<Question> }
QuestionAdapter
import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView import com.codesenior.period.tracker.R import com.codesenior.period.tracker.databinding.RecyclerviewQuestionsBinding import com.codesenior.period.tracker.models.Question class QuestionAdapter(private val questions: List<Question>) : RecyclerView.Adapter<QuestionAdapter.QuestionViewHolder>() { override fun getItemCount() = questions.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionViewHolder { return QuestionViewHolder( DataBindingUtil.inflate( LayoutInflater.from(parent.context), R.layout.recyclerview_questions, parent, false ) ) } override fun onBindViewHolder(holder: QuestionViewHolder, position: Int) { holder.recyclerView.question = questions[position] } inner class QuestionViewHolder(val recyclerView: RecyclerviewQuestionsBinding) : RecyclerView.ViewHolder(recyclerView.root) }