Merge pull request #7 from hakodeveloper/structure/implement-navigation

Implement navigation
This commit is contained in:
Carlos Martinez
2020-02-05 21:16:19 -03:00
committed by GitHub
27 changed files with 234 additions and 61 deletions

View File

@@ -8,21 +8,26 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.hako.albumlist.R
import com.hako.albumlist.model.AlbumViewable
import com.hako.albumlist.navigation.AlbumlistNavigation
import com.hako.albumlist.viewmodel.AlbumlistViewmodel
import com.hako.albumlist.widget.AlbumlistAdapter
import com.hako.base.domain.network.RequestStatus
import com.hako.base.extensions.gone
import com.hako.base.extensions.observeNonNull
import com.hako.base.extensions.toast
import com.hako.base.extensions.visible
import com.hako.base.navigation.NavigationRouter
import kotlinx.android.synthetic.main.fragment_albumlist.*
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
const val ALBUMLIST_FRAGMENT_BUNDLE_USER_ID = "ALBUMLIST_FRAGMENT_BUNDLE_USER_ID"
class AlbumlistFragment : Fragment() {
private val viewModel: AlbumlistViewmodel by viewModel()
private val listAdapter by lazy { AlbumlistAdapter() }
private val navigation: NavigationRouter by inject()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@@ -32,8 +37,12 @@ class AlbumlistFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
setRecycler()
setObservers()
// TODO: Get user by bundle
viewModel.fetchAlbums(2)
doRequest()
}
private fun doRequest() {
arguments?.getInt(ALBUMLIST_FRAGMENT_BUNDLE_USER_ID)?.let { viewModel.fetchAlbums(it) } ?:
throw UninitializedPropertyAccessException("The UserId is expected but it wasn't provided")
}
private fun setObservers() {
@@ -72,7 +81,7 @@ class AlbumlistFragment : Fragment() {
layoutManager = LinearLayoutManager(context)
adapter = listAdapter.apply {
onItemClick = {
context.toast(it.title)
navigation.sendNavigation(AlbumlistNavigation.ClickedOnAlbum(it.id))
}
}
}

View File

@@ -0,0 +1,7 @@
package com.hako.albumlist.navigation
import com.hako.base.navigation.NavigationEvent
sealed class AlbumlistNavigation : NavigationEvent {
data class ClickedOnAlbum(val albumId: Int) : AlbumlistNavigation()
}

View File

@@ -9,6 +9,10 @@
android:id="@+id/albumlistFragment"
android:name="com.hako.albumlist.feature.AlbumlistFragment"
tools:layout="@layout/fragment_albumlist"
android:label="AlbumlistFragment" />
android:label="{actionTitle}">
<argument
android:name="actionTitle"
app:argType="string" />
</fragment>
</navigation>

View File

@@ -15,6 +15,7 @@
<activity
android:name=".view.MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -3,8 +3,11 @@ package com.hako.friendlists.di
import androidx.room.Room
import com.hako.base.domain.database.DatabaseClient
import com.hako.base.domain.network.RemoteClient
import com.hako.base.navigation.NavigationRouter
import com.hako.friendlists.BuildConfig
import com.hako.friendlists.viewmodel.NavigationViewmodel
import com.squareup.picasso.Picasso
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val appModules = module {
@@ -19,4 +22,8 @@ val appModules = module {
// Picasso
single { Picasso.get() }
// Navigation
single { NavigationRouter() }
viewModel { NavigationViewmodel() }
}

View File

@@ -3,20 +3,51 @@ package com.hako.friendlists.view
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController
import com.hako.base.extensions.observeNonNull
import com.hako.base.navigation.NavigationRouter
import com.hako.friendlists.BuildConfig
import com.hako.friendlists.R
import com.hako.friendlists.viewmodel.NavigationViewmodel
import com.squareup.picasso.Picasso
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
class MainActivity : AppCompatActivity() {
private val navController by lazy { findNavController(R.id.main_fragment_container) }
private val navRouter: NavigationRouter by inject()
private val picasso: Picasso by inject()
private val viewModel: NavigationViewmodel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupNavigation()
setupPicasso()
}
private fun setupNavigation() {
navController.setGraph(R.navigation.main_navigation)
navRouter.setOnNavigationEvent {
viewModel.onNavigationEvent(it)
}
viewModel.navigate.observeNonNull(this) { pair ->
// Pair.first is a Navigation Id
// Pair.second is a Bundle
navController.navigate(pair.first, pair.second)
}
setupActionBarWithNavController(this, navController)
}
private fun setupPicasso() {
// Show cache indicator on images just for debug builds
picasso.setIndicatorsEnabled(BuildConfig.DEBUG)
}
override fun onSupportNavigateUp(): Boolean {
navController.navigateUp()
return super.onSupportNavigateUp()
}
}

View File

@@ -0,0 +1,50 @@
package com.hako.friendlists.viewmodel
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.hako.albumlist.feature.ALBUMLIST_FRAGMENT_BUNDLE_USER_ID
import com.hako.albumlist.navigation.AlbumlistNavigation
import com.hako.base.extensions.buildNavigation
import com.hako.base.navigation.NavigationEvent
import com.hako.friendlists.R
import com.hako.photolist.feature.PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID
import com.hako.userlist.navigation.UserlistNavigation
// This sets the fragment title, it's referenced in every navigation
const val FRAGMENT_TITLE = "actionTitle"
class NavigationViewmodel : ViewModel() {
val navigate = MutableLiveData<Pair<@IdRes Int, Bundle>>()
fun onNavigationEvent(event: NavigationEvent) {
when (event) {
is UserlistNavigation -> handleUserlistNavigation(event)
is AlbumlistNavigation -> handleAlbumlistNavigation(event)
else -> throw NoWhenBranchMatchedException("Undefined navigation event parent")
}
}
private fun handleUserlistNavigation(event: UserlistNavigation) {
when (event) {
is UserlistNavigation.ClickedOnUser -> navigate.postValue(
buildNavigation(R.id.action_userlistFragment_to_albumlistFragment, Bundle().apply {
putInt(ALBUMLIST_FRAGMENT_BUNDLE_USER_ID, event.userId)
putString(FRAGMENT_TITLE, event.userName)
})
)
}
}
private fun handleAlbumlistNavigation(event: AlbumlistNavigation) {
when (event) {
is AlbumlistNavigation.ClickedOnAlbum -> navigate.postValue(
buildNavigation(R.id.action_albumlistFragment_to_photolistFragment, Bundle().apply {
putInt(PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID, event.albumId)
})
)
}
}
}

View File

@@ -1,8 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"/>
android:layout_height="match_parent">
<fragment
android:id="@+id/main_fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,11 +2,25 @@
<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/photolist_navigation">
app:startDestination="@id/userlist_navigation">
<include app:graph="@navigation/userlist_navigation" />
<action
android:id="@+id/action_userlistFragment_to_albumlistFragment"
app:destination="@id/albumlist_navigation"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
<include app:graph="@navigation/albumlist_navigation" />
<action
android:id="@+id/action_albumlistFragment_to_photolistFragment"
app:destination="@id/photolist_navigation"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
<include app:graph="@navigation/photolist_navigation" />

View File

@@ -10,4 +10,4 @@ fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) {
func(it)
}
})
}
}

View File

@@ -1,23 +0,0 @@
package com.hako.base.extensions
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
fun <T> RecyclerView.Adapter<*>.autoNotify(oldList: List<T>, newList: List<T>, compare: (T, T) -> Boolean) {
val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return compare(oldList[oldItemPosition], newList[newItemPosition])
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
})
diff.dispatchUpdatesTo(this)
}

View File

@@ -1,19 +1,6 @@
package com.hako.base.extensions
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.NavHostFragment
fun AppCompatActivity.findNavHostFragment(@IdRes id: Int) =
supportFragmentManager.findFragmentById(id) as NavHostFragment
fun Fragment.findNavHostFragment(@IdRes id: Int) =
childFragmentManager.findFragmentById(id) as NavHostFragment
fun Fragment.findNavController(@IdRes id: Int) =
androidx.navigation.Navigation.findNavController(view?.findViewById(id) ?: viewNotFound(id, this))
private fun viewNotFound(@IdRes id: Int, fragment: Fragment): Nothing = throw IllegalStateException(
"View ID $id at '${fragment::class.java.simpleName}' not found."
)
fun buildNavigation(@IdRes id: Int, bundle: Bundle = Bundle()) = Pair(id, bundle)

View File

@@ -4,6 +4,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
fun View.enable() {
isEnabled = true
@@ -37,3 +39,22 @@ fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): Vi
LayoutInflater
.from(context)
.inflate(layout, this, attachToRoot)
fun <T> RecyclerView.Adapter<*>.autoNotify(oldList: List<T>, newList: List<T>, compare: (T, T) -> Boolean) {
val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return compare(oldList[oldItemPosition], newList[newItemPosition])
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
})
diff.dispatchUpdatesTo(this)
}

View File

@@ -0,0 +1,7 @@
package com.hako.base.navigation
interface NavigationEvent
interface NavigationController {
fun sendNavigation(event: NavigationEvent)
}

View File

@@ -0,0 +1,13 @@
package com.hako.base.navigation
class NavigationRouter : NavigationController {
private var onNavigationEvent: (NavigationEvent) -> Unit = {}
override fun sendNavigation(event: NavigationEvent) {
onNavigationEvent(event)
}
fun setOnNavigationEvent(listener: (NavigationEvent) -> Unit) {
onNavigationEvent = listener
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="-100%p" android:toXDelta="0"
android:duration="@android:integer/config_shortAnimTime"/>
</set>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0"
android:duration="@android:integer/config_shortAnimTime"/>
</set>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-100%p"
android:duration="@android:integer/config_shortAnimTime"/>
</set>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="100%p"
android:duration="@android:integer/config_shortAnimTime"/>
</set>

View File

@@ -18,6 +18,8 @@ import kotlinx.android.synthetic.main.fragment_photolist.*
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
const val PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID = "PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID"
class PhotolistFragment : Fragment() {
private val viewModel: PhotolistViewmodel by viewModel()
@@ -31,8 +33,12 @@ class PhotolistFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
setRecycler()
setObservers()
// TODO: Get album by bundle
viewModel.fetchPhotos(1)
doRequest()
}
private fun doRequest() {
arguments?.getInt(PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID)?.let { viewModel.fetchPhotos(it) } ?:
throw UninitializedPropertyAccessException("The AlbumId is expected but it wasn't provided")
}
private fun setObservers() {

View File

@@ -47,10 +47,6 @@ class PhotoViewHolder(private val view: View) :
private val picasso: Picasso by inject()
init {
picasso.setIndicatorsEnabled(true)
}
fun bind(photo: PhotoViewable) = with(view) {
picasso.load(photo.photoUrl)
.placeholder(R.drawable.img_photo_placeholder)

View File

@@ -35,9 +35,11 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="2"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -9,6 +9,6 @@
android:id="@+id/photolistFragment"
android:name="com.hako.photolist.feature.PhotolistFragment"
tools:layout="@layout/fragment_photolist"
android:label="PhotolistFragment" />
android:label="Photos" />
</navigation>

View File

@@ -11,11 +11,14 @@ import com.hako.base.extensions.gone
import com.hako.base.extensions.observeNonNull
import com.hako.base.extensions.toast
import com.hako.base.extensions.visible
import com.hako.base.navigation.NavigationRouter
import com.hako.userlist.model.UserViewable
import com.hako.userlist.viewmodel.UserlistViewmodel
import com.hako.userlist.widget.UserlistAdapter
import com.hako.friendlist_userlist.R
import com.hako.userlist.navigation.UserlistNavigation
import kotlinx.android.synthetic.main.fragment_userlist.*
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
@@ -23,6 +26,7 @@ class UserlistFragment : Fragment() {
private val viewModel: UserlistViewmodel by viewModel()
private val listAdapter by lazy { UserlistAdapter() }
private val navigation: NavigationRouter by inject()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@@ -71,7 +75,7 @@ class UserlistFragment : Fragment() {
layoutManager = LinearLayoutManager(context)
adapter = listAdapter.apply {
onItemClick = {
context.toast(it.realName)
navigation.sendNavigation(UserlistNavigation.ClickedOnUser(it.id, it.realName))
}
onFavoriteClick = {

View File

@@ -0,0 +1,7 @@
package com.hako.userlist.navigation
import com.hako.base.navigation.NavigationEvent
sealed class UserlistNavigation : NavigationEvent {
data class ClickedOnUser(val userId: Int, val userName: String) : UserlistNavigation()
}

View File

@@ -17,7 +17,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginTop="12dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -9,6 +9,6 @@
android:id="@+id/userlistFragment"
android:name="com.hako.userlist.feature.UserlistFragment"
tools:layout="@layout/fragment_userlist"
android:label="UserlistFragment" />
android:label="Friendlist" />
</navigation>