Skip to content

Commit 5a2bea1

Browse files
committed
show post details
1 parent f584b78 commit 5a2bea1

24 files changed

+257
-25
lines changed

app/build.gradle

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
id 'kotlinx-serialization'
55
id 'kotlin-kapt'
66
id 'dagger.hilt.android.plugin'
7+
id 'androidx.navigation.safeargs'
78
}
89

910
android {
@@ -47,12 +48,15 @@ dependencies {
4748
implementation 'androidx.appcompat:appcompat:1.4.0'
4849
implementation 'com.google.android.material:material:1.4.0'
4950
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
50-
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
51-
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
51+
5252
testImplementation 'junit:junit:4.13.2'
5353
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
5454
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
5555

56+
//Navigation
57+
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
58+
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
59+
5660
// Hilt dependencies
5761
implementation "com.google.dagger:hilt-android:$hilt_version"
5862
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,47 @@
11
package com.kishorebabu.gorillaschallenge.ui.details
22

3+
import android.os.Bundle
4+
import android.util.Log
5+
import android.view.View
36
import androidx.fragment.app.Fragment
47
import androidx.fragment.app.viewModels
58
import com.kishorebabu.gorillaschallenge.R
9+
import com.kishorebabu.gorillaschallenge.databinding.FragmentPostDetailsBinding
10+
import com.kishorebabu.gorillaschallenge.ui.UiState
11+
import com.kishorebabu.posts.domain.model.Post
12+
import com.kishorebabu.posts.domain.model.PostWithUser
613
import dagger.hilt.android.AndroidEntryPoint
714

815
@AndroidEntryPoint
916
class PostDetailsFragment : Fragment(R.layout.fragment_post_details) {
1017
private val viewModel by viewModels<PostDetailsViewModel>()
18+
private lateinit var binding: FragmentPostDetailsBinding
19+
20+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
21+
super.onViewCreated(view, savedInstanceState)
22+
binding = FragmentPostDetailsBinding.bind(view)
23+
24+
arguments?.getParcelable<Post>("post")?.let {
25+
viewModel.onPostDetails(it)
26+
viewModel.uiState.observe(viewLifecycleOwner) {
27+
when (it) {
28+
is UiState.Content -> renderContent(it.data)
29+
is UiState.Error -> Log.e("asdf", "Error", it.throwable)
30+
UiState.Loading -> Log.d("asdf", "Loading...")
31+
}
32+
}
33+
}
34+
}
35+
36+
private fun renderContent(postWithUser: PostWithUser) {
37+
with(postWithUser) {
38+
binding.tvTitle.text = post?.title
39+
binding.tvUser.text = getString(
40+
R.string.user_name_format,
41+
user?.name,
42+
user?.username,
43+
)
44+
binding.tvBody.text = post?.body
45+
}
46+
}
1147
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
package com.kishorebabu.gorillaschallenge.ui.details
22

3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
35
import androidx.lifecycle.ViewModel
6+
import com.kishorebabu.gorillaschallenge.ui.UiState
7+
import com.kishorebabu.posts.domain.model.Post
8+
import com.kishorebabu.posts.domain.model.PostWithUser
9+
import com.kishorebabu.posts.domain.usecase.GetPostWithUserDetailsUseCase
10+
import dagger.hilt.android.lifecycle.HiltViewModel
11+
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
12+
import io.reactivex.rxjava3.schedulers.Schedulers
13+
import javax.inject.Inject
414

5-
class PostDetailsViewModel : ViewModel() {
15+
@HiltViewModel
16+
class PostDetailsViewModel @Inject constructor(
17+
private val getPostWithUserDetailsUseCase: GetPostWithUserDetailsUseCase
18+
) : ViewModel() {
19+
private val uiStateLiveData = MutableLiveData<UiState<PostWithUser>>()
20+
val uiState: LiveData<UiState<PostWithUser>> = uiStateLiveData
21+
22+
23+
fun onPostDetails(post: Post) {
24+
getPostWithUserDetailsUseCase
25+
.invoke(post.id, post.user)
26+
.subscribeOn(Schedulers.io())
27+
.observeOn(AndroidSchedulers.mainThread())
28+
.subscribe(
29+
{
30+
it.fold(
31+
success = {
32+
uiStateLiveData.postValue(UiState.Content(it))
33+
}, failure = {
34+
uiStateLiveData.postValue(UiState.Error(it))
35+
}
36+
)
37+
},
38+
{
39+
uiStateLiveData.postValue(UiState.Error(it))
40+
}
41+
)
42+
}
643
}

app/src/main/java/com/kishorebabu/gorillaschallenge/ui/list/PostsListAdapter.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import com.kishorebabu.posts.domain.model.Post
1010

1111
class PostsListAdapter : ListAdapter<Post, PostsListAdapter.PostItemViewHolder>(PostListDiffUtil) {
1212
private val postsList = mutableListOf<Post>()
13-
private var postOnClickListener: (String) -> Unit = {}
13+
private var postOnClickListener: (Post) -> Unit = {}
1414

15-
fun setPosts(posts: List<Post>, onClick: (String) -> Unit) {
15+
fun setPosts(posts: List<Post>, onClick: (Post) -> Unit) {
1616
postsList.clear()
1717
postsList.addAll(posts)
1818
this.postOnClickListener = onClick
@@ -36,11 +36,11 @@ class PostsListAdapter : ListAdapter<Post, PostsListAdapter.PostItemViewHolder>(
3636
class PostItemViewHolder constructor(
3737
private val binding: ItemPostListBinding
3838
) : RecyclerView.ViewHolder(binding.root) {
39-
fun bind(post: Post, postOnClickListener: (String) -> Unit) {
39+
fun bind(post: Post, postOnClickListener: (Post) -> Unit) {
4040
binding.apply {
4141
this.tvBody.text = post.body
4242
this.tvTitle.text = post.title
43-
root.setOnClickListener { postOnClickListener(post.id) }
43+
root.setOnClickListener { postOnClickListener(post) }
4444
}
4545
}
4646
}

app/src/main/java/com/kishorebabu/gorillaschallenge/ui/list/PostsListFragment.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.util.Log
55
import android.view.View
66
import androidx.fragment.app.Fragment
77
import androidx.fragment.app.viewModels
8+
import androidx.navigation.fragment.findNavController
89
import androidx.recyclerview.widget.LinearLayoutManager
910
import androidx.recyclerview.widget.RecyclerView
1011
import com.kishorebabu.gorillaschallenge.R
@@ -29,8 +30,9 @@ class PostsListFragment : Fragment(R.layout.fragment_posts_list) {
2930
is UiState.Content -> {
3031
postsListAdapter.setPosts(
3132
it.data
32-
) {
33-
Log.d("asdf", "Post id $it clicked")
33+
) { post ->
34+
this.findNavController()
35+
.navigate(PostsListFragmentDirections.actionPostDetails(post))
3436
}
3537
}
3638
is UiState.Error -> Log.e("asdf", "Error", it.throwable)

app/src/main/java/com/kishorebabu/gorillaschallenge/ui/list/PostsListViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class PostsListViewModel @Inject constructor(
1717
private val getAllPostsUseCase: GetAllPostsUseCase
1818
) : ViewModel() {
1919

20-
private val uiStateLiveData = MutableLiveData<UiState<List<Post>>>(UiState.Loading)
20+
private val uiStateLiveData = MutableLiveData<UiState<List<Post>>>()
2121
val uiState: LiveData<UiState<List<Post>>> = uiStateLiveData
2222

2323
fun onViewReady() {
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
45
android:layout_width="match_parent"
5-
android:layout_height="match_parent">
6+
android:layout_height="match_parent"
7+
android:orientation="vertical"
8+
android:padding="16dp">
69

710
<TextView
8-
android:layout_width="wrap_content"
11+
android:id="@+id/tv_title"
12+
android:layout_width="match_parent"
13+
android:layout_height="wrap_content"
14+
tools:text="@tools:sample/lorem" />
15+
16+
<Space
17+
android:layout_width="match_parent"
18+
android:layout_height="4dp" />
19+
20+
<TextView
21+
android:id="@+id/tv_user"
22+
android:layout_width="match_parent"
923
android:layout_height="wrap_content"
10-
android:text="Details!"
11-
app:layout_constraintBottom_toBottomOf="parent"
12-
app:layout_constraintEnd_toEndOf="parent"
1324
app:layout_constraintStart_toStartOf="parent"
14-
app:layout_constraintTop_toTopOf="parent" />
25+
app:layout_constraintTop_toTopOf="parent"
26+
tools:text="@tools:sample/full_names" />
27+
28+
<Space
29+
android:layout_width="match_parent"
30+
android:layout_height="16dp" />
31+
32+
<TextView
33+
android:id="@+id/tv_body"
34+
android:layout_width="match_parent"
35+
android:layout_height="wrap_content"
36+
tools:text="@tools:sample/lorem/random" />
1537

16-
</androidx.constraintlayout.widget.ConstraintLayout>
38+
</androidx.appcompat.widget.LinearLayoutCompat>

app/src/main/res/navigation/navigation.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
android:name="com.kishorebabu.gorillaschallenge.ui.details.PostDetailsFragment"
2525
tools:layout="@layout/fragment_post_details">
2626
<argument
27-
android:name="postId"
28-
app:argType="java.lang.String" />
27+
android:name="post"
28+
app:argType="com.kishorebabu.posts.domain.model.Post" />
2929

3030
</fragment>
3131
</navigation>

app/src/main/res/values/strings.xml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<resources>
22
<string name="app_name">GorillasChallenge</string>
3+
<string name="user_name_format">%1$s (%2$s)</string>
34
</resources>

build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
buildscript {
33
ext.hilt_version = '2.40'
4+
ext.nav_version = '2.3.5'
45

56
dependencies {
67
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
8+
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
79
}
810
}
911

posts/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'com.android.library'
33
id 'org.jetbrains.kotlin.android'
44
id 'kotlinx-serialization'
5+
id 'kotlin-parcelize'
56
id 'kotlin-kapt'
67
id 'dagger.hilt.android.plugin'
78
}

posts/src/main/java/com/kishorebabu/posts/data/network/model/AddressDto.kt

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.kishorebabu.posts.data.network.model
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class AddressDto(
47
val city: String,
58
val geo: GeoDto,

posts/src/main/java/com/kishorebabu/posts/data/network/model/CompanyDto.kt

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.kishorebabu.posts.data.network.model
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class CompanyDto(
47
val bs: String,
58
val catchPhrase: String,

posts/src/main/java/com/kishorebabu/posts/data/network/model/GeoDto.kt

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.kishorebabu.posts.data.network.model
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class GeoDto(
47
val lat: String,
58
val lng: String

posts/src/main/java/com/kishorebabu/posts/data/network/model/UserDto.kt

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.kishorebabu.posts.data.network.model
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class UserDto(
47
val address: AddressDto,
58
val company: CompanyDto,
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.kishorebabu.posts.domain.model
22

3+
import android.os.Parcelable
4+
import kotlinx.android.parcel.Parcelize
5+
6+
@Parcelize
37
data class Post(
48
val body: String,
59
val id: String,
610
val title: String,
711
val user: String
8-
)
12+
) : Parcelable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.kishorebabu.posts.domain.model
2+
3+
data class PostWithUser(
4+
var id: String? = null,
5+
var post: Post? = null,
6+
var user: User? = null,
7+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.kishorebabu.posts.domain.usecase
2+
3+
import com.kishorebabu.core.SimpleResult
4+
import com.kishorebabu.posts.domain.model.Post
5+
import io.reactivex.rxjava3.core.Single
6+
7+
interface GetPostByIdUseCase {
8+
operator fun invoke(id: String): Single<SimpleResult<Post>>
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.kishorebabu.posts.domain.usecase
2+
3+
import com.kishorebabu.core.SimpleResult
4+
import com.kishorebabu.posts.domain.model.Post
5+
import com.kishorebabu.posts.domain.repository.PostRepository
6+
import io.reactivex.rxjava3.core.Single
7+
import javax.inject.Inject
8+
9+
class GetPostByIdUseCaseImpl @Inject constructor(
10+
private val postRepository: PostRepository
11+
) : GetPostByIdUseCase {
12+
override fun invoke(id: String): Single<SimpleResult<Post>> {
13+
return postRepository.getPostById(id)
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.kishorebabu.posts.domain.usecase
2+
3+
import com.kishorebabu.core.SimpleResult
4+
import com.kishorebabu.posts.domain.model.PostWithUser
5+
import io.reactivex.rxjava3.core.Single
6+
7+
interface GetPostWithUserDetailsUseCase {
8+
operator fun invoke(postId: String, userId: String): Single<SimpleResult<PostWithUser>>
9+
}

0 commit comments

Comments
 (0)