Retrofit, Coroutines, ModelViewModel, Fragment, RecyclerView Usage In Kotlin

19-05-2020

Fragment

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)
}

© 2019 All rights reserved. Codesenior.COM