Skip to content

Commit 35410ba

Browse files
RUM-9511: Locations and LocationDetails screens
1 parent 03e1549 commit 35410ba

20 files changed

+967
-0
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ kronosNTP = "0.0.1-alpha11"
88
kotlinxSerialization = "1.6.3"
99

1010
# Android
11+
adapterDelegates = "4.3.2"
1112
androidDesugaringSdk = "2.0.4"
1213
androidToolsPlugin = "8.9.1"
1314
androidXAnnotations = "1.9.1"
@@ -134,6 +135,7 @@ kronosNTP = { module = "com.lyft.kronos:kronos-android", version.ref = "kronosNT
134135
assertJ = { module = "org.assertj:assertj-core", version.ref = "assertJ" }
135136

136137
# Android libs
138+
adapterDelegatesViewBinding = { module = "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding", version.ref = "adapterDelegates" }
137139
androidDesugaringSdk = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugaringSdk" }
138140
androidXAnnotation = { module = "androidx.annotation:annotation", version.ref = "androidXAnnotations" }
139141
androidXAppCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidXAppCompat" }

sample/benchmark/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependencies {
8585
implementation(libs.kotlin)
8686

8787
// Android dependencies
88+
implementation(libs.adapterDelegatesViewBinding)
8889
implementation(libs.androidXMultidex)
8990
implementation(libs.bundles.androidXNavigation)
9091
implementation(libs.androidXAppCompat)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.common.details
8+
9+
import com.bumptech.glide.Glide
10+
import com.datadog.benchmark.sample.network.rickandmorty.models.Character
11+
import com.datadog.benchmark.sample.utils.recycler.BaseRecyclerViewItem
12+
import com.datadog.sample.benchmark.databinding.ItemCharacterBinding
13+
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
14+
15+
internal data class CharacterItem(
16+
val character: Character,
17+
override val key: String
18+
) : BaseRecyclerViewItem
19+
20+
internal fun characterItemDelegate(onClick: (Character) -> Unit) =
21+
adapterDelegateViewBinding<CharacterItem, BaseRecyclerViewItem, ItemCharacterBinding>(
22+
{ layoutInflater, root -> ItemCharacterBinding.inflate(layoutInflater, root, false) }
23+
) {
24+
bind {
25+
Glide.with(binding.root.context)
26+
.load(item.character.image)
27+
.into(binding.characterImage)
28+
29+
binding.characterName.text = item.character.name
30+
binding.root.setOnClickListener {
31+
onClick(item.character)
32+
}
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails
8+
9+
import com.datadog.benchmark.sample.ui.rumauto.screens.common.details.characterItemDelegate
10+
import com.datadog.benchmark.sample.utils.recycler.BaseRecyclerViewItem
11+
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
12+
import javax.inject.Inject
13+
14+
internal class RumAutoLocationDetailsAdapter @Inject constructor(
15+
private val viewModel: RumAutoLocationDetailsViewModel
16+
) : ListDelegationAdapter<List<BaseRecyclerViewItem>>() {
17+
18+
init {
19+
delegatesManager.apply {
20+
addDelegate(
21+
characterItemDelegate { viewModel.dispatch(RumAutoLocationDetailsAction.OnCharacterClicked(it)) }
22+
)
23+
}
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails
8+
9+
import android.os.Bundle
10+
import android.view.LayoutInflater
11+
import android.view.View
12+
import android.view.ViewGroup
13+
import androidx.fragment.app.Fragment
14+
import androidx.lifecycle.lifecycleScope
15+
import androidx.lifecycle.viewModelScope
16+
import androidx.recyclerview.widget.GridLayoutManager
17+
import com.datadog.benchmark.sample.activities.scenarios.benchmarkActivityComponent
18+
import com.datadog.benchmark.sample.navigation.args
19+
import com.datadog.benchmark.sample.network.rickandmorty.models.Location
20+
import com.datadog.benchmark.sample.ui.rumauto.screens.common.details.CharacterItem
21+
import com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails.di.DaggerRumAutoLocationDetailsComponent
22+
import com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails.di.RumAutoLocationDetailsComponent
23+
import com.datadog.benchmark.sample.utils.componentHolderViewModel
24+
import com.datadog.benchmark.sample.utils.recycler.applyNewItems
25+
import com.datadog.sample.benchmark.databinding.FragmentRumAutoLocationDetailsBinding
26+
import kotlinx.coroutines.flow.launchIn
27+
import kotlinx.coroutines.flow.onEach
28+
import javax.inject.Inject
29+
30+
internal class RumAutoLocationDetailsFragment : Fragment() {
31+
32+
private val location: Location by args()
33+
34+
private val component: RumAutoLocationDetailsComponent by componentHolderViewModel {
35+
DaggerRumAutoLocationDetailsComponent.factory().create(
36+
deps = requireActivity().benchmarkActivityComponent,
37+
location = location,
38+
viewModelScope = viewModelScope
39+
)
40+
}
41+
42+
@Inject
43+
lateinit var viewModel: RumAutoLocationDetailsViewModel
44+
45+
@Inject
46+
lateinit var adapter: RumAutoLocationDetailsAdapter
47+
48+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
49+
component.inject(this)
50+
51+
val binding = FragmentRumAutoLocationDetailsBinding.inflate(inflater, container, false)
52+
53+
@Suppress("MagicNumber")
54+
binding.locationDetailsRecycler.layoutManager = GridLayoutManager(requireContext(), 3)
55+
binding.locationDetailsRecycler.adapter = adapter
56+
57+
viewModel.state
58+
.onEach { state ->
59+
binding.locationDetailsTitle.text = state.location.name
60+
binding.locationDetailsType.text = state.location.type
61+
binding.locationDetailsDimension.text = state.location.dimension
62+
binding.locationDetailsCreated.text = state.location.created
63+
64+
val characters = state
65+
.residentsLoadingTask
66+
.optionalResult
67+
?.optionalResult
68+
?.map {
69+
CharacterItem(
70+
character = it,
71+
key = it.id.toString()
72+
)
73+
} ?: emptyList()
74+
75+
adapter.applyNewItems(characters)
76+
}
77+
.launchIn(lifecycleScope)
78+
79+
return binding.root
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails
8+
9+
import com.datadog.benchmark.sample.di.common.CoroutineDispatcherQualifier
10+
import com.datadog.benchmark.sample.di.common.CoroutineDispatcherType
11+
import com.datadog.benchmark.sample.network.KtorHttpResponse
12+
import com.datadog.benchmark.sample.network.rickandmorty.RickAndMortyNetworkService
13+
import com.datadog.benchmark.sample.network.rickandmorty.models.Character
14+
import com.datadog.benchmark.sample.network.rickandmorty.models.Location
15+
import com.datadog.benchmark.sample.ui.rumauto.RumAutoScenarioNavigator
16+
import com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails.di.RumAutoLocationDetailsScope
17+
import com.datadog.benchmark.sample.utils.BenchmarkAsyncTask
18+
import com.datadog.benchmark.sample.utils.StateMachine
19+
import kotlinx.coroutines.CoroutineDispatcher
20+
import kotlinx.coroutines.CoroutineScope
21+
import kotlinx.coroutines.Job
22+
import kotlinx.coroutines.flow.StateFlow
23+
import kotlinx.coroutines.launch
24+
import javax.inject.Inject
25+
26+
internal data class RumAutoLocationDetailsState(
27+
val location: Location,
28+
val residentsLoadingTask: BenchmarkAsyncTask<KtorHttpResponse<List<Character>>, ResidentsLoadingTask>
29+
) {
30+
class ResidentsLoadingTask(val ids: List<String>)
31+
}
32+
33+
internal sealed interface RumAutoLocationDetailsAction {
34+
data class ResidentsLoadingFinished(
35+
val response: KtorHttpResponse<List<Character>>,
36+
val task: RumAutoLocationDetailsState.ResidentsLoadingTask
37+
) : RumAutoLocationDetailsAction
38+
39+
data class OnCharacterClicked(val character: Character) : RumAutoLocationDetailsAction
40+
}
41+
42+
@RumAutoLocationDetailsScope
43+
internal class RumAutoLocationDetailsViewModel @Inject constructor(
44+
private val navigator: RumAutoScenarioNavigator,
45+
private val location: Location,
46+
@CoroutineDispatcherQualifier(CoroutineDispatcherType.Default) private val defaultDispatcher: CoroutineDispatcher,
47+
private val viewModelScope: CoroutineScope,
48+
private val rickAndMortyNetworkService: RickAndMortyNetworkService
49+
) {
50+
51+
private val stateMachine = StateMachine.create(
52+
initialState = RumAutoLocationDetailsState(
53+
residentsLoadingTask = run {
54+
val task = RumAutoLocationDetailsState.ResidentsLoadingTask(characterIds(location))
55+
val job = launchResidentsLoadingTask(task)
56+
BenchmarkAsyncTask.Loading(job = job, key = task)
57+
},
58+
location = location
59+
),
60+
dispatcher = defaultDispatcher,
61+
scope = viewModelScope,
62+
processAction = ::processAction
63+
)
64+
65+
val state: StateFlow<RumAutoLocationDetailsState> get() = stateMachine.state
66+
67+
fun dispatch(action: RumAutoLocationDetailsAction) {
68+
stateMachine.dispatch(action)
69+
}
70+
71+
private fun processAction(
72+
prev: RumAutoLocationDetailsState,
73+
action: RumAutoLocationDetailsAction
74+
): RumAutoLocationDetailsState {
75+
return when (action) {
76+
is RumAutoLocationDetailsAction.OnCharacterClicked -> {
77+
viewModelScope.launch {
78+
navigator.openCharacterScreen(action.character)
79+
}
80+
prev
81+
}
82+
83+
else -> prev.copy(
84+
residentsLoadingTask = processTask(prev, action)
85+
)
86+
}
87+
}
88+
89+
private fun processTask(
90+
prev: RumAutoLocationDetailsState,
91+
action: RumAutoLocationDetailsAction
92+
): BenchmarkAsyncTask<KtorHttpResponse<List<Character>>, RumAutoLocationDetailsState.ResidentsLoadingTask> {
93+
return when (action) {
94+
is RumAutoLocationDetailsAction.ResidentsLoadingFinished -> {
95+
BenchmarkAsyncTask.Result(action.response, action.task)
96+
}
97+
98+
else -> prev.residentsLoadingTask
99+
}
100+
}
101+
102+
private fun characterIds(location: Location): List<String> {
103+
return location.residents.mapNotNull { it.split("/").lastOrNull() }
104+
}
105+
106+
private fun launchResidentsLoadingTask(task: RumAutoLocationDetailsState.ResidentsLoadingTask): Job {
107+
return viewModelScope.launch(defaultDispatcher) {
108+
val response = rickAndMortyNetworkService.getCharacters(task.ids)
109+
dispatch(RumAutoLocationDetailsAction.ResidentsLoadingFinished(response, task))
110+
}
111+
}
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails.di
8+
9+
import com.datadog.benchmark.sample.di.common.DispatchersModule
10+
import com.datadog.benchmark.sample.network.rickandmorty.RickAndMortyNetworkService
11+
import com.datadog.benchmark.sample.network.rickandmorty.models.Location
12+
import com.datadog.benchmark.sample.ui.rumauto.RumAutoScenarioNavigator
13+
import com.datadog.benchmark.sample.ui.rumauto.screens.locationdetails.RumAutoLocationDetailsFragment
14+
import dagger.BindsInstance
15+
import dagger.Component
16+
import kotlinx.coroutines.CoroutineScope
17+
import javax.inject.Scope
18+
19+
internal interface RumAutoLocationDetailsComponentDependencies {
20+
val rickAndMortyNetworkService: RickAndMortyNetworkService
21+
val rumAutoScenarioNavigator: RumAutoScenarioNavigator
22+
}
23+
24+
@Scope
25+
internal annotation class RumAutoLocationDetailsScope
26+
27+
@RumAutoLocationDetailsScope
28+
@Component(
29+
dependencies = [
30+
RumAutoLocationDetailsComponentDependencies::class
31+
],
32+
modules = [
33+
DispatchersModule::class
34+
]
35+
)
36+
internal interface RumAutoLocationDetailsComponent {
37+
@Component.Factory
38+
interface Factory {
39+
fun create(
40+
deps: RumAutoLocationDetailsComponentDependencies,
41+
@BindsInstance viewModelScope: CoroutineScope,
42+
@BindsInstance location: Location
43+
): RumAutoLocationDetailsComponent
44+
}
45+
46+
fun inject(fragment: RumAutoLocationDetailsFragment)
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locations
8+
9+
import com.datadog.benchmark.sample.utils.recycler.BaseRecyclerViewItem
10+
import com.datadog.sample.benchmark.databinding.ItemLocationBinding
11+
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
12+
13+
internal data class RumAutoLocationItem(
14+
val title: String,
15+
val firstSubtitle: String,
16+
val secondSubtitle: String,
17+
val locationId: Int,
18+
override val key: String
19+
) : BaseRecyclerViewItem
20+
21+
internal fun rumAutoLocationDelegate(
22+
onLocationClicked: (Int) -> Unit
23+
) = adapterDelegateViewBinding<RumAutoLocationItem, BaseRecyclerViewItem, ItemLocationBinding>(
24+
{ layoutInflater, root -> ItemLocationBinding.inflate(layoutInflater, root, false) }
25+
) {
26+
bind {
27+
binding.locationTitle.text = item.title
28+
binding.locationFirstSubtitle.text = item.firstSubtitle
29+
binding.locationSecondSubtitle.text = item.secondSubtitle
30+
binding.root.setOnClickListener {
31+
onLocationClicked(item.locationId)
32+
}
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.benchmark.sample.ui.rumauto.screens.locations
8+
9+
import com.datadog.benchmark.sample.utils.recycler.BaseRecyclerViewItem
10+
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
11+
import javax.inject.Inject
12+
13+
internal class RumAutoLocationsAdapter @Inject constructor(
14+
private val viewModel: RumAutoLocationsViewModel
15+
) : ListDelegationAdapter<List<BaseRecyclerViewItem>>() {
16+
init {
17+
delegatesManager.addDelegate(
18+
rumAutoLocationDelegate { viewModel.dispatch(RumAutoLocationsAction.OnLocationClicked(it)) }
19+
)
20+
}
21+
}

0 commit comments

Comments
 (0)