create module and implement it

This commit is contained in:
Carlos Martinez
2020-02-04 21:51:45 -03:00
parent 885978495e
commit c1efba5d63
31 changed files with 421 additions and 58 deletions
@@ -3,7 +3,7 @@ package com.hako.albumlist.domain.usecase
import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi
import com.hako.albumlist.model.AlbumViewable
import com.hako.albumlist.model.toAlbumEntity
import com.hako.albumlist.model.toUserViewable
import com.hako.albumlist.model.toAlbumViewable
import com.hako.base.domain.database.dao.AlbumDao
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
@@ -28,13 +28,13 @@ class GetAlbum(private val dao: AlbumDao) : KoinComponent {
api.getAlbums(userId)
.doOnSuccess {
dao.saveAll(it.map { album -> album.toAlbumEntity() })
onSuccess(dao.getAlbums(userId).map { album -> album.toUserViewable() })
onSuccess(dao.getAlbums(userId).map { album -> album.toAlbumViewable() })
}
.doOnSubscribe { onLoading() }
.subscribeOn(Schedulers.io())
.subscribe({}, { onError(it) })
} else {
onSuccess(dbAlbum.map { it.toUserViewable() })
onSuccess(dbAlbum.map { it.toAlbumViewable() })
}
}
.subscribe()
@@ -20,5 +20,5 @@ data class AlbumViewable(
fun Album.toAlbumEntity() = AlbumEntity(this.id, this.userId, this.title)
fun AlbumEntity.toUserViewable() = AlbumViewable(this.id, this.userId, this.title)
fun AlbumEntity.toAlbumViewable() = AlbumViewable(this.id, this.userId, this.title)
@@ -17,10 +17,10 @@ class AlbumlistViewmodel : ViewModel(), KoinComponent {
val data = MutableLiveData<Either<Throwable, List<AlbumViewable>>>()
val requestStatus = MutableLiveData<RequestStatus>()
private val getUsers: GetAlbum = get()
private val getAlbum: GetAlbum = get()
fun fetchAlbums(userId: Int) {
getUsers.execute(
getAlbum.execute(
userId,
onSuccess = {
requestStatus.postValue(Ready)
@@ -20,7 +20,7 @@ class AlbumlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var onItemClick: (AlbumViewable) -> Unit = { }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
UserViewHolder(
AlbumViewHolder(
LayoutInflater
.from(parent.context)
.inflate(R.layout.item_album_card, parent, false),
@@ -37,12 +37,12 @@ class AlbumlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) =
when (viewholder) {
is UserViewHolder -> viewholder.bind(items[position])
is AlbumViewHolder -> viewholder.bind(items[position])
else -> throw NoWhenBranchMatchedException("Undefined viewholder")
}
}
class UserViewHolder(private val view: View,
class AlbumViewHolder(private val view: View,
private val onItemClick: (AlbumViewable) -> Unit) :
RecyclerView.ViewHolder(view) {
@@ -14,12 +14,15 @@
<TextView
android:id="@+id/item_album_card_album_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Album name" />
@@ -9,6 +9,6 @@
android:id="@+id/albumlistFragment"
android:name="com.hako.albumlist.feature.AlbumlistFragment"
tools:layout="@layout/fragment_albumlist"
android:label="AlbumListFragment" />
android:label="AlbumlistFragment" />
</navigation>
+1
View File
@@ -50,4 +50,5 @@ dependencies {
implementation project(":base")
implementation project(":userlist")
implementation project(":albumlist")
implementation project(":photolist")
}
@@ -4,6 +4,7 @@ import android.app.Application
import com.hako.albumlist.di.albumListModules
import com.hako.userlist.di.userlistModules
import com.hako.friendlists.di.appModules
import com.hako.photolist.di.photoListModules
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
@@ -28,7 +29,8 @@ class MainApplication : Application() {
listOf(
appModules,
userlistModules,
albumListModules
albumListModules,
photoListModules
)
)
}
@@ -2,10 +2,12 @@
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_navigation"
app:startDestination="@id/userlist_navigation">
app:startDestination="@id/photolist_navigation">
<include app:graph="@navigation/userlist_navigation" />
<include app:graph="@navigation/albumlist_navigation" />
<include app:graph="@navigation/photolist_navigation" />
</navigation>
@@ -1,24 +0,0 @@
package com.hako.base
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.hako.base.test", appContext.packageName)
}
}
@@ -18,6 +18,9 @@ interface PhotoDao {
@get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}")
val all: List<PhotoEntity>
@Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC")
fun getPhotos(albumId: Int): List<PhotoEntity>
@Query("SELECT COUNT(*) FROM ${PhotoEntity.TABLE_NAME}")
fun count(): Int
@@ -1,17 +0,0 @@
package com.hako.base
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+1
View File
@@ -0,0 +1 @@
/build
+6
View File
@@ -0,0 +1,6 @@
apply plugin: 'com.android.library'
apply from: '../core.gradle'
dependencies {
implementation project(':base')
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+2
View File
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hako.photolist" />
@@ -0,0 +1,15 @@
package com.hako.photolist.di
import com.hako.base.domain.network.RemoteClient
import com.hako.photolist.domain.datasource.PhotolistRemoteApi
import com.hako.photolist.domain.usecase.GetPhoto
import com.hako.photolist.viewmodel.PhotolistViewmodel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val photoListModules = module {
factory { get<RemoteClient>().getClient(PhotolistRemoteApi::class.java) }
factory { GetPhoto(get()) }
viewModel { PhotolistViewmodel() }
}
@@ -0,0 +1,14 @@
package com.hako.photolist.domain.datasource
import com.hako.photolist.model.Photo
import io.reactivex.Single
import retrofit2.http.GET
import retrofit2.http.Query
interface PhotolistRemoteApi {
@GET("/photos")
fun getPhotos(
@Query("albumId") albumId: Int
): Single<List<Photo>>
}
@@ -0,0 +1,40 @@
package com.hako.photolist.domain.usecase
import com.hako.base.domain.database.dao.PhotoDao
import com.hako.photolist.domain.datasource.PhotolistRemoteApi
import com.hako.photolist.model.*
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.koin.core.KoinComponent
import org.koin.core.get
class GetPhoto(private val dao: PhotoDao) : KoinComponent {
private val api: PhotolistRemoteApi = get()
fun execute(
albumId: Int,
onSuccess: (List<PhotoViewable>) -> Unit,
onError: (Throwable) -> Unit,
onLoading: () -> Unit
) {
Single.fromCallable { dao.getPhotos(albumId) }
.subscribeOn(Schedulers.io())
.doOnError { onError(it) }
.doOnSuccess { dbAlbum ->
if (dbAlbum.isEmpty() || dbAlbum.count() == 0) {
api.getPhotos(albumId)
.doOnSuccess {
dao.saveAll(it.map { album -> album.toPhotoEntity() })
onSuccess(dao.getPhotos(albumId).map { album -> album.toPhotoViewable() })
}
.doOnSubscribe { onLoading() }
.subscribeOn(Schedulers.io())
.subscribe({}, { onError(it) })
} else {
onSuccess(dbAlbum.map { it.toPhotoViewable() })
}
}
.subscribe()
}
}
@@ -0,0 +1,75 @@
package com.hako.photolist.feature
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.hako.base.domain.network.RequestStatus
import com.hako.base.extensions.gone
import com.hako.base.extensions.observeNonNull
import com.hako.base.extensions.visible
import com.hako.photolist.R
import com.hako.photolist.model.PhotoViewable
import com.hako.photolist.viewmodel.PhotolistViewmodel
import com.hako.photolist.widget.PhotolistAdapter
import kotlinx.android.synthetic.main.fragment_photolist.*
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
class PhotolistFragment : Fragment() {
private val viewModel: PhotolistViewmodel by viewModel()
private val listAdapter by lazy { PhotolistAdapter() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_photolist, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setRecycler()
setObservers()
// TODO: Get album by bundle
viewModel.fetchPhotos(2)
}
private fun setObservers() {
viewModel.data.observeNonNull(this) {
it.either(::handleFetchError, ::handleFetchSuccess)
}
viewModel.requestStatus.observeNonNull(this) {
when (it) {
RequestStatus.Ready -> {
fragment_photolist_error_overlay.gone()
fragment_photolist_loading_overlay.gone()
}
RequestStatus.Loading -> {
fragment_photolist_error_overlay.gone()
fragment_photolist_loading_overlay.visible()
}
RequestStatus.Errored -> {
fragment_photolist_error_overlay.visible()
fragment_photolist_loading_overlay.gone()
}
}
}
}
private fun handleFetchError(throwable: Throwable) {
Timber.e(throwable)
}
private fun handleFetchSuccess(photos: List<PhotoViewable>) {
listAdapter.addAll(photos)
}
private fun setRecycler() {
fragment_photolist_recycler_container.apply {
layoutManager = LinearLayoutManager(context)
adapter = listAdapter
}
}
}
@@ -0,0 +1,27 @@
package com.hako.photolist.model
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import com.hako.base.domain.database.entities.PhotoEntity
import kotlinx.android.parcel.Parcelize
@Parcelize
data class Photo(
@SerializedName("id") val id: Int,
@SerializedName("albumId") val albumId: Int,
@SerializedName("title") val title: String,
@SerializedName("url") val photoUrl: String,
@SerializedName("thumbnailUrl") val thumbnailUrl: String
) : Parcelable
data class PhotoViewable(
val id: Int,
val albumId: Int,
val title: String,
val photoUrl: String
)
fun Photo.toPhotoEntity() = PhotoEntity(this.id, this.albumId, this.title, this.photoUrl, this.thumbnailUrl)
fun PhotoEntity.toPhotoViewable() = PhotoViewable(this.id, this.albumId, this.title, this.photoUrl)
@@ -0,0 +1,37 @@
package com.hako.photolist.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.hako.base.domain.network.RequestStatus
import com.hako.base.domain.network.RequestStatus.Ready
import com.hako.base.domain.network.RequestStatus.Loading
import com.hako.base.domain.network.RequestStatus.Errored
import com.hako.base.domain.Either
import com.hako.photolist.domain.usecase.GetPhoto
import com.hako.photolist.model.PhotoViewable
import org.koin.core.KoinComponent
import org.koin.core.get
class PhotolistViewmodel : ViewModel(), KoinComponent {
val data = MutableLiveData<Either<Throwable, List<PhotoViewable>>>()
val requestStatus = MutableLiveData<RequestStatus>()
private val getPhoto: GetPhoto = get()
fun fetchPhotos(albumId: Int) {
getPhoto.execute(
albumId,
onSuccess = {
requestStatus.postValue(Ready)
data.postValue(Either.Right(it))
},
onLoading = {
requestStatus.postValue(Loading)
},
onError = {
requestStatus.postValue(Errored)
data.postValue(Either.Left(it))
})
}
}
@@ -0,0 +1,50 @@
package com.hako.photolist.widget
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hako.base.extensions.autoNotify
import com.hako.photolist.R
import com.hako.photolist.model.PhotoViewable
import kotlinx.android.synthetic.main.item_photo_card.view.*
import kotlin.properties.Delegates
class PhotolistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items by Delegates.observable(emptyList<PhotoViewable>()) { _, oldList, newList ->
autoNotify(oldList, newList) { old, new -> old.id == new.id }
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
PhotoViewHolder(
LayoutInflater
.from(parent.context)
.inflate(R.layout.item_photo_card, parent, false)
)
fun getItem(position: Int) = items[position]
fun addAll(list: List<PhotoViewable>) {
items = list
}
override fun getItemCount() = items.size
override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) =
when (viewholder) {
is PhotoViewHolder -> viewholder.bind(items[position])
else -> throw NoWhenBranchMatchedException("Undefined viewholder")
}
}
class PhotoViewHolder(private val view: View) :
RecyclerView.ViewHolder(view) {
fun bind(photo: PhotoViewable) = with(view) {
item_photo_card_title.text = photo.title
// TODO load image
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/fragment_photolist_base">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fragment_photolist_recycler_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.hako.base.widgets.LoadingOverlay
android:id="@+id/fragment_photolist_loading_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.hako.base.widgets.NetworkErrorOverlay
android:id="@+id/fragment_photolist_error_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_photo_card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_card"
android:elevation="2dp"
android:orientation="vertical">
<ImageView
android:id="@+id/item_photo_card_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/img_photo_placeholder"
android:contentDescription="Album photo" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/item_photo_card_footer_container"
android:layout_width="match_parent"
android:layout_height="70dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/item_photo_card_photo">
<TextView
android:id="@+id/item_photo_card_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Album name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/photolist_navigation"
app:startDestination="@id/photolistFragment">
<fragment
android:id="@+id/photolistFragment"
android:name="com.hako.photolist.feature.PhotolistFragment"
tools:layout="@layout/fragment_photolist"
android:label="PhotolistFragment" />
</navigation>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">photolist</string>
</resources>
+1 -1
View File
@@ -1,2 +1,2 @@
include ':app', ':base', ':userlist', ':albumlist'
include ':app', ':base', ':userlist', ':albumlist', ':photolist'
rootProject.name='Friendlists'
@@ -3,12 +3,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/userlist_navigation"
app:startDestination="@id/userListFragment">
app:startDestination="@id/userlistFragment">
<fragment
android:id="@+id/userListFragment"
android:id="@+id/userlistFragment"
android:name="com.hako.userlist.feature.UserlistFragment"
tools:layout="@layout/fragment_userlist"
android:label="UserListFragment" />
android:label="UserlistFragment" />
</navigation>