<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/domain/interactor/GetPostByIdInteractor.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.domain.interactor

import com.carlosgub.coroutines.core.interactor.Interactor
import com.carlosgub.coroutines.features.books.domain.model.PostEntity
import com.carlosgub.coroutines.features.books.domain.repository.PostRepository
import kotlinx.coroutines.flow.Flow

class GetPostByIdInteractor(
    private val postRepository: PostRepository
) : Interactor<GetPostByIdInteractor.Params, Flow<PostEntity>> {

    override fun execute(params: Params): Flow<PostEntity> {
        return postRepository.getPostById(id = params.id)
    }

    data class Params(val id: String)
}

<|start_filename|>app/src/test/java/com/carlosgub/coroutines/utils/TestUtils.kt<|end_filename|>
package com.carlosgub.coroutines.utils

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

// Informacion
// https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04

@VisibleForTesting
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 3,
    timeUnit: TimeUnit = TimeUnit.SECONDS
): T? {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    if (!latch.await(time, timeUnit)) {
        throw TimeoutException("Value was never set")
    }
    return data
}


<|start_filename|>app/src/test/java/com/carlosgub/coroutines/TestData.kt<|end_filename|>
package com.carlosgub.coroutines

import com.carlosgub.coroutines.features.books.domain.model.PostEntity

object TestData {
    val dataEmpty = emptyList<PostEntity>()
    val dataList = listOf(
        PostEntity(1, 1, "<NAME>", "First book"),
        PostEntity(2, 2, "<NAME>", "Second book"),
        PostEntity(3, 3, "<NAME>", "Third book")
    )
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/model/mapper/PostVMMapper.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.model.mapper

import com.carlosgub.coroutines.core.mapper.Mapper
import com.carlosgub.coroutines.features.books.domain.model.PostEntity
import com.carlosgub.coroutines.features.books.presentation.model.PostVM

class PostVMMapper : Mapper<PostEntity, PostVM> {
    override fun map(origin: PostEntity) =
        PostVM(
            userId = origin.userId,
            id = origin.id,
            title = origin.title,
            body = origin.body
        )
}

<|start_filename|>app/src/test/java/com/carlosgub/coroutines/PostsViewModelTest.kt<|end_filename|>
package com.carlosgub.coroutines

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import com.carlosgub.coroutines.core.interactor.Interactor
import com.carlosgub.coroutines.features.books.domain.interactor.GetPostsInteractor
import com.carlosgub.coroutines.features.books.presentation.viewmodel.PostsViewModel
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostVS
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostsVS
import com.carlosgub.coroutines.utils.CoroutinesRule
import com.carlosgub.coroutines.utils.getOrAwaitValue
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.asFlow
import org.junit.*

class PostsViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @ExperimentalCoroutinesApi
    @get:Rule
    var coroutinesRule = CoroutinesRule()

    private val useCaseMock = mockk<GetPostsInteractor>()
    private lateinit var viewModel: PostsViewModel
    private val observer = mockk<Observer<PostsVS>>(relaxed = true)

    @Before
    fun setup() {
        viewModel = PostsViewModel(useCaseMock)
        viewModel.viewState.observeForever(observer)
    }

    @Test
    fun `retrieve posts successful`() {
        coEvery { useCaseMock.execute(Interactor.None) } returns TestData.dataList.asFlow()
        viewModel.getPosts()
        viewModel.viewState.getOrAwaitValue()
        verify(exactly = 3) { observer.onChanged(any<PostsVS.AddPost>()) }
    }

    @Test
    fun `retrieve posts failure`() {
        coEvery { useCaseMock.execute(Interactor.None) } throws UnsupportedOperationException()
        viewModel.getPosts()
        viewModel.viewState.getOrAwaitValue()
        verify { observer.onChanged(any<PostsVS.Error>()) }
    }

    @Suppress("UNREACHABLE_CODE")
    @Test(expected = UnsupportedOperationException::class)
    fun `retrieve posts with exception`() {
        coEvery { useCaseMock.execute(Interactor.None) } returns throw UnsupportedOperationException()
        viewModel.getPosts()
    }
}


<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/activities/PostsActivity.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.activities

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.carlosgub.coroutines.R
import com.carlosgub.coroutines.features.books.presentation.adapters.RVPostAdapter
import com.carlosgub.coroutines.features.books.presentation.viewmodel.PostsViewModel
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostsVS
import kotlinx.android.synthetic.main.posts_activity.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.koin.androidx.viewmodel.ext.android.viewModel

class PostsActivity : AppCompatActivity(), RVPostAdapter.Listener {

    private val viewModel: PostsViewModel by viewModel()
    private val mAdapter = RVPostAdapter()

    @ExperimentalCoroutinesApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.posts_activity)
        observeViewModel()
        mAdapter.setListener(this)

        rvPost.apply {
            layoutManager = LinearLayoutManager(this@PostsActivity)
            adapter = mAdapter
        }

        viewModel.getPosts()
    }

    private fun observeViewModel(){
        //Obtener los Posts
        viewModel.viewState.observe(this@PostsActivity, {
            when (it) {
                is PostsVS.AddPost -> {
                    mAdapter.add(it.postVM)
                }
                is PostsVS.ShowLoader ->{
                    if(it.showLoader){
                        pbPosts.visibility = View.VISIBLE
                        rvPost.visibility = View.GONE
                    }else{
                        pbPosts.visibility = View.GONE
                        rvPost.visibility = View.VISIBLE
                    }
                }
                is PostsVS.Error -> {
                    Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
                }
            }

        })
    }

    override fun onPostClicked(id: Int) {
        startActivity(PostActivity.createInstance(context = this, id = id.toString()))
    }
}


<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/mapper/PostDataMapper.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.mapper

import com.carlosgub.coroutines.core.mapper.Mapper
import com.carlosgub.coroutines.features.books.data.datasource.rest.response.PostResponseData
import com.carlosgub.coroutines.features.books.domain.model.PostEntity

class PostDataMapper : Mapper<PostResponseData, PostEntity> {
    override fun map(origin: PostResponseData) =
        PostEntity(
            userId = origin.userId,
            id = origin.id,
            title = origin.title,
            body = origin.body
        )
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/core/network/BaseApiClient.kt<|end_filename|>
package com.carlosgub.coroutines.core.network

import com.carlosgub.coroutines.BuildConfig
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

open class BaseApiClient<T>(private val classT: Class<T>) {

    companion object{
        const val CONNECTION_TIMEOUT: Long = 180L
        const val READ_TIMEOUT: Long = 180L
        const val WRITE_TIMEOUT: Long = 180L
    }

    open fun getApiClient(): T {
        val httpLoggingInterceptor = HttpLoggingInterceptor()
        httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

        val okHttpClient = OkHttpClient().newBuilder()
            .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
            .addInterceptor(httpLoggingInterceptor)
            .build()

        val retrofitBuilder = Retrofit.Builder().apply {
            baseUrl(BuildConfig.URL_SERVER)
            client(okHttpClient)
            addConverterFactory(GsonConverterFactory.create())
        }


        val retrofit = retrofitBuilder.build()
        return retrofit.create(classT)
    }
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/adapters/RVPostAdapter.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.adapters

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.carlosgub.coroutines.R
import com.carlosgub.coroutines.features.books.presentation.model.PostVM
import kotlinx.android.synthetic.main.post_item.view.*

class RVPostAdapter : RecyclerView.Adapter<RVPostAdapter.ViewHolder>() {

    private val mList: MutableList<PostVM> = mutableListOf()
    private var mListener: Listener? = null

    fun add(lstPostVM: PostVM) {
        mList.add(lstPostVM)
        notifyItemInserted(this.itemCount)
    }

    fun setListener(listener: Listener) {
        mListener = listener
    }

    interface Listener {
        fun onPostClicked(id: Int)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
        LayoutInflater.from(parent.context)
            .inflate(R.layout.post_item, parent, false)
    )

    override fun getItemCount() = mList.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(mList[position])
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(postVM: PostVM) {
            itemView.tvPostItemTitle.text = postVM.title
            itemView.cvPostItem.setOnClickListener {
                mListener?.onPostClicked(postVM.id ?: 1)
            }
        }
    }
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/domain/repository/PostRepository.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.domain.repository

import com.carlosgub.coroutines.features.books.domain.model.PostEntity
import kotlinx.coroutines.flow.Flow

interface PostRepository {
    fun getPosts(): Flow<PostEntity>
    fun getPostById(id: String): Flow<PostEntity>
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/datasource/rest/response/PostResponseData.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.datasource.rest.response

data class PostResponseData(
    val userId: Int?,
    val id: Int?,
    val title: String?,
    val body: String?
)

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/datasource/rest/PostRestDataStore.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.datasource.rest

import com.carlosgub.coroutines.features.books.data.datasource.rest.response.PostResponseData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class PostRestDataStore {

    fun getPosts(): Flow<PostResponseData> = flow {
        PostApiClient.getApiClient().getPosts().forEach {
            emit(it)
        }
    }


    fun getPostById(id: String): Flow<PostResponseData> = flow {
        emit(PostApiClient.getApiClient().getPostById(id = id))
    }

}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/activities/PostActivity.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.activities

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.carlosgub.coroutines.R
import com.carlosgub.coroutines.features.books.presentation.viewmodel.PostViewModel
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostVS
import kotlinx.android.synthetic.main.post_activity.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.koin.androidx.viewmodel.ext.android.viewModel

class PostActivity : AppCompatActivity() {

    private val viewModel: PostViewModel by viewModel()
    private lateinit var id: String

    companion object {
        private const val ARG_ID = "arg_id"
        fun createInstance(context: Context, id: String): Intent {
            return Intent(context, PostActivity::class.java).putExtras(
                Bundle().apply {
                    putString(ARG_ID, id)
                }
            )
        }
    }

    @ExperimentalCoroutinesApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.post_activity)
        observeViewModel()
        intent.extras.let {
            id = it?.getString(ARG_ID) ?: "-1"
            viewModel.getPostByID(id)
        }

    }

    private fun observeViewModel() {
        viewModel.viewState.observe(this@PostActivity, {
            when (it) {
                is PostVS.Post -> {
                    tvPostITitle.text = String.format(getString(R.string.post_text),it.postVM.title,it.postVM.body)
                }
                is PostVS.ShowLoader ->{
                    if(it.showLoader){
                        pbPost.visibility = View.VISIBLE
                        gPost.visibility = View.GONE
                    }else{
                        pbPost.visibility = View.GONE
                        gPost.visibility = View.VISIBLE
                    }
                }
                is PostVS.Error -> {
                    Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
                }
            }
        })
    }
}


<|start_filename|>app/src/main/java/com/carlosgub/coroutines/core/platform/BaseViewModel.kt<|end_filename|>
package com.carlosgub.coroutines.core.platform

import androidx.lifecycle.ViewModel
import org.koin.core.KoinComponent

abstract class BaseViewModel : ViewModel(), KoinComponent {
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/domain/interactor/GetPostsInteractor.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.domain.interactor

import com.carlosgub.coroutines.core.interactor.Interactor
import com.carlosgub.coroutines.features.books.domain.model.PostEntity
import com.carlosgub.coroutines.features.books.domain.repository.PostRepository
import kotlinx.coroutines.flow.Flow

class GetPostsInteractor(
    private val postRepository: PostRepository
) : Interactor<Interactor.None, Flow<PostEntity>> {

    override fun execute(params: Interactor.None): Flow<PostEntity> {
        return postRepository.getPosts()
    }
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/datasource/rest/ApiClient.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.datasource.rest

import com.carlosgub.coroutines.core.network.BaseApiClient
import com.carlosgub.coroutines.features.books.data.datasource.rest.interfaces.IPostApiClient

object PostApiClient : BaseApiClient<IPostApiClient>(IPostApiClient::class.java)

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/viewmodel/state/PostVS.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.viewmodel.state

import com.carlosgub.coroutines.features.books.presentation.model.PostVM

sealed class PostVS {
    class Post(val postVM: PostVM):PostVS()
    class Error(val message:String?):PostVS()
    class ShowLoader(val showLoader:Boolean):PostVS()
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/viewmodel/state/PostsVS.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.viewmodel.state

import com.carlosgub.coroutines.features.books.presentation.model.PostVM

sealed class PostsVS {
    class AddPost(val postVM: PostVM):PostsVS()
    class Error(val message:String?):PostsVS()
    class ShowLoader(val showLoader:Boolean):PostsVS()
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/viewmodel/PostsViewModel.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.carlosgub.coroutines.core.interactor.Interactor
import com.carlosgub.coroutines.core.platform.BaseViewModel
import com.carlosgub.coroutines.core.utils.io
import com.carlosgub.coroutines.core.utils.ui
import com.carlosgub.coroutines.features.books.domain.interactor.GetPostsInteractor
import com.carlosgub.coroutines.features.books.presentation.model.mapper.PostVMMapper
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostsVS
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class PostsViewModel(
    private val getPostsInteractor: GetPostsInteractor
) : BaseViewModel() {

    val viewState: LiveData<PostsVS> get() = mViewState
    private val mViewState = MutableLiveData<PostsVS>()

    private val mPostVMMapper by lazy { PostVMMapper() }

    fun getPosts() {
        viewModelScope.launch {
            mViewState.value = PostsVS.ShowLoader(true)
            try {
                io {
                    getPostsInteractor.execute(Interactor.None)
                        .collect {
                            ui { mViewState.value = PostsVS.AddPost(mPostVMMapper.map(it)) }
                        }
                }
            } catch (e: Exception) {
                ui { mViewState.value = PostsVS.Error(e.message) }
            }
            mViewState.value = PostsVS.ShowLoader(false)
        }
    }
}

<|start_filename|>app/src/test/java/com/carlosgub/coroutines/learning/MockkLearning.kt<|end_filename|>
package com.carlosgub.coroutines.learning

import io.mockk.*
import junit.framework.Assert.assertEquals
import org.junit.Test

/**
 * @author <NAME>
 * @see https://blog.kotlin-academy.com/mocking-is-not-rocket-science-mockk-features-e5d55d735a98
 */

class MockkLearning {
    class Divider {
        fun divide(a: Int, b: Int) = a / b
    }

    @Test
    fun `trying slot by mockK`() {
        val slot = slot<Int>() //Almacena un valor
        val mock = mockk<Divider>()

        /**
         * Si se usa la funcion divide de la clase Divider
         * este regresara 22, pero aparte el capture(slot) obtendra el valor
         * ingresado en el primer parametro y lo guardara en la variable slot
         * PD: el any() es que no importa el segundo valor traera 22, el capture(x)
         * tambien seria como un any() pero que captura el valor ingresado
         */
        every { mock.divide(capture(slot), any()) } returns 22

        /**
         * Se esta llamando la clase divide y cualquier valor que se ingrese
         * regresara 22, ahora como se ingreso 5 en el primer parametro se le asignara
         * ese valor a la variable slot
         * slot <- 5
         */
        mock.divide(5, 2)

        /**
         * Verificamos que el valor asignado a slot sea 5
         * Si es 5 el Test pasara sin problema
         */
        assertEquals(5, slot.captured)
    }

    @Test
    fun `trying slot by mockK with answers`() {
        val slot = slot<Int>() //Almacena un valor
        val mock = mockk<Divider>()

        /**
         * Es la misma explicacion del ejercicio anterior,
         * la diferencia que el valor a retornar si llamamos la
         * funcion divide seria el valor del primer parametro * 11
         */
        every {
            mock.divide(capture(slot), any())
        } answers {
            slot.captured * 11
        }

        /**
         * Es la misma explicacion del ejercicio anterior,
         * solo que ahora el valor que regresa es 55
         */
        val result = mock.divide(5, 2)

        /**
         * Verificamos que la variable result tenga asignado el valor 55
         */
        assertEquals(55, result)
    }

    @Test
    fun `trying slot like mutableList by mockK with `() {
        val slot = mutableListOf<Int>() //Almacena un conjunto de valores
        val mock = mockk<Divider>()

        /**
         * En este caso slot puede almacenar muchas variables
         */
        every {
            mock.divide(capture(slot), any())
        } answers {
            slot[0] * 11
        }

        /**
         * Es la misma explicacion del ejercicio anterior
         */
        val result = mock.divide(5, 2)

        /**
         * Verificamos que la variable result tenga asignado el valor 55
         */
        assertEquals(55, result)
    }

    @Test
    fun `trying relaxed mocks when we dont want to declare some return in every callback `() {
        /**
         * Usamos (relaxed = true) cuando no usaremos el every, ya que
         * no sabemos el comportamiento de la clase que estamos 'mockeando' y queremos que mockK
         * nos retorne un valor basico como 0 o null
         */
        val mock = mockk<Divider>(relaxed = true)

        /**
         * Verificamos que la division sea 0 (porque la funcion regresa un entero (INT) y el defecto es 0)
         * , ya que no hemos agregado el valor que deberia regrear anteriormente.
         */
        verify(exactly = 0) { mock.divide(5, 2) }
    }

    class Adder {
        fun magnify(a: Int) = a

        fun add(a: Int, b: Int) = a + magnify(b)
    }

    @Test
    fun `trying Spies `() {

        val spy = spyk(Adder())

        /**
         * Modifica la funcion magnify de la clase Adder regresando el valor de magnify * 2
         */
        every { spy.magnify(any()) } answers { firstArg<Int>() * 2 }

        /**
         * Verificamos si el valor 5 se multiplica por 2, por lo anterior dicho
         * y al sumarse con el 4 el resultado deberia ser 14.
         */
        assertEquals(14, spy.add(4, 5))
    }


}


<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/presentation/viewmodel/PostViewModel.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.presentation.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.carlosgub.coroutines.core.platform.BaseViewModel
import com.carlosgub.coroutines.core.utils.io
import com.carlosgub.coroutines.core.utils.ui
import com.carlosgub.coroutines.features.books.domain.interactor.GetPostByIdInteractor
import com.carlosgub.coroutines.features.books.presentation.model.mapper.PostVMMapper
import com.carlosgub.coroutines.features.books.presentation.viewmodel.state.PostVS
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class PostViewModel(
    private val getPostByIdInteractor: GetPostByIdInteractor
) : BaseViewModel() {

    val viewState: LiveData<PostVS> get() = mViewState
    private val mViewState = MutableLiveData<PostVS>()

    private val mPostVMMapper by lazy { PostVMMapper() }

    fun getPostByID(id: String) {
        viewModelScope.launch {
            mViewState.value = PostVS.ShowLoader(true)
            try {
                io {
                    getPostByIdInteractor.execute(GetPostByIdInteractor.Params(id = id))
                        .collect {
                            ui {
                                mViewState.value = PostVS.Post(mPostVMMapper.map(it))
                            }
                        }
                }
            } catch (e: Exception) {
                ui {
                    mViewState.value = PostVS.Error(e.message)
                }
            }
            mViewState.value = PostVS.ShowLoader(false)
        }
    }
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/core/mapper/Mapper.kt<|end_filename|>
package com.carlosgub.coroutines.core.mapper


interface Mapper<in T, out R> {
    fun map(origin: T): R

    fun map(origin: List<T>): List<R> {
        return origin.map(::map)
    }
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/repository/PostRepositoryImpl.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.repository

import com.carlosgub.coroutines.features.books.data.datasource.rest.PostRestDataStore
import com.carlosgub.coroutines.features.books.data.mapper.PostDataMapper
import com.carlosgub.coroutines.features.books.domain.model.PostEntity
import com.carlosgub.coroutines.features.books.domain.repository.PostRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class PostRepositoryImpl(
    private val postRestDataStore: PostRestDataStore
) : PostRepository {

    private val mPostDataMapper by lazy { PostDataMapper() }

    override fun getPosts(): Flow<PostEntity> =
        postRestDataStore.getPosts().map{
            mPostDataMapper.map(it)
        }



    override fun getPostById(id: String): Flow<PostEntity> =
        postRestDataStore.getPostById(id = id).map {
            mPostDataMapper.map(it)
        }
}


<|start_filename|>app/src/main/java/com/carlosgub/coroutines/core/interactor/Interactor.kt<|end_filename|>
package com.carlosgub.coroutines.core.interactor

interface Interactor<in Params, out Type> {

    fun execute(params: Params): Type

    object None
}

<|start_filename|>app/src/main/java/com/carlosgub/coroutines/features/books/data/datasource/rest/interfaces/IPostApiClient.kt<|end_filename|>
package com.carlosgub.coroutines.features.books.data.datasource.rest.interfaces
import com.carlosgub.coroutines.features.books.data.datasource.rest.response.PostResponseData
import retrofit2.http.GET
import retrofit2.http.Path

interface IPostApiClient {
    @GET("/posts")
    suspend fun getPosts(): List<PostResponseData>

    @GET("/posts/{id}")
    suspend fun getPostById(@Path("id") id:String): PostResponseData
}