diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 105d65d8..932469a9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
android:name=".presentation.tip.TipActivity"
android:exported="false" />
(R.layout.activity_pokemon) {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- if (savedInstanceState == null) {
- supportFragmentManager.commit {
- replace(R.id.fragment_container_pokemon)
- }
- }
- }
-}
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListFragment.kt b/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListActivity.kt
similarity index 72%
rename from android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListFragment.kt
rename to android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListActivity.kt
index dcda74bd..552f1cd9 100644
--- a/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListFragment.kt
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListActivity.kt
@@ -2,23 +2,22 @@ package poke.rogue.helper.presentation.dex
import android.content.res.Configuration
import android.os.Bundle
-import android.view.View
+import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
-import androidx.fragment.app.commit
-import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import poke.rogue.helper.R
import poke.rogue.helper.data.repository.DefaultDexRepository
-import poke.rogue.helper.databinding.FragmentPokemonListBinding
-import poke.rogue.helper.presentation.base.error.ErrorHandleFragment
+import poke.rogue.helper.databinding.ActivityPokemonListBinding
+import poke.rogue.helper.presentation.base.error.ErrorHandleActivity
import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel
import poke.rogue.helper.presentation.dex.detail.PokemonDetailActivity
+import poke.rogue.helper.presentation.util.activity.hideKeyboard
import poke.rogue.helper.presentation.util.repeatOnStarted
import poke.rogue.helper.presentation.util.view.GridSpacingItemDecoration
import poke.rogue.helper.presentation.util.view.dp
-class PokemonListFragment :
- ErrorHandleFragment(R.layout.fragment_pokemon_list) {
+class PokemonListActivity :
+ ErrorHandleActivity(R.layout.activity_pokemon_list) {
private val viewModel by viewModels {
PokemonListViewModel.factory(
DefaultDexRepository.instance(),
@@ -34,16 +33,15 @@ class PokemonListFragment :
override val toolbar: Toolbar
get() = binding.toolbarDex
- override fun onViewCreated(
- view: View,
- savedInstanceState: Bundle?,
- ) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
binding.viewModel = viewModel
- binding.lifecycleOwner = viewLifecycleOwner
-
+ binding.lifecycleOwner = this
initAdapter()
initObservers()
+ binding.root.setOnClickListener {
+ hideKeyboard()
+ }
}
private fun initAdapter() {
@@ -70,14 +68,13 @@ class PokemonListFragment :
}
repeatOnStarted {
viewModel.navigateToDetailEvent.collect { pokemonId ->
- parentFragmentManager.commit {
- startActivity(PokemonDetailActivity.intent(requireContext(), pokemonId))
- }
+ hideKeyboard()
+ startActivity(PokemonDetailActivity.intent(this, pokemonId))
}
}
}
companion object {
- val TAG: String = PokemonListFragment::class.java.simpleName
+ val TAG: String = PokemonListActivity::class.java.simpleName
}
}
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListViewModel.kt b/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListViewModel.kt
index 0f9f0270..86f1a954 100644
--- a/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListViewModel.kt
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/dex/PokemonListViewModel.kt
@@ -10,18 +10,17 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import poke.rogue.helper.analytics.AnalyticsLogger
import poke.rogue.helper.analytics.analyticsLogger
+import poke.rogue.helper.data.exception.PokeException
import poke.rogue.helper.data.model.Pokemon
import poke.rogue.helper.data.repository.DexRepository
import poke.rogue.helper.presentation.base.BaseViewModelFactory
@@ -35,33 +34,26 @@ class PokemonListViewModel(
) : ErrorHandleViewModel(logger), PokemonListNavigateHandler, PokemonQueryHandler {
private val searchQuery = MutableStateFlow("")
- private val _isLoading = MutableStateFlow(false)
- val isLoading: StateFlow = _isLoading.asStateFlow()
-
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val uiState: StateFlow> =
- refreshEvent
+ merge(refreshEvent.map { "" }, searchQuery)
.onStart {
- _isLoading.value = true
- emit(Unit)
+ if (isEmpty.value) {
+ _isLoading.value = true
+ }
}
- .flatMapLatest {
- searchQuery
- .debounce(300L)
- .mapLatest { query ->
- queriedPokemons(query)
- }
- .catch { e ->
- handlePokemonError(e)
- }
- }.onEach {
- _isLoading.value = false
+ .debounce(300L)
+ .mapLatest { query ->
+ queriedPokemons(query)
}
.stateIn(
viewModelScope + errorHandler,
SharingStarted.WhileSubscribed(5000),
emptyList(),
)
+ private val _isLoading = MutableStateFlow(false)
+ val isLoading: StateFlow = _isLoading.asStateFlow()
+
val isEmpty: StateFlow =
uiState.map { it.isEmpty() && !isLoading.value }
.stateIn(
@@ -74,10 +66,18 @@ class PokemonListViewModel(
val navigateToDetailEvent = _navigateToDetailEvent.asSharedFlow()
private suspend fun queriedPokemons(query: String): List {
- if (query.isEmpty()) {
- return pokemonListRepository.pokemons().map(Pokemon::toUi)
+ return try {
+ if (query.isEmpty()) {
+ pokemonListRepository.pokemons().map(Pokemon::toUi)
+ } else {
+ pokemonListRepository.pokemons(query).map(Pokemon::toUi)
+ }
+ } catch (e: PokeException) {
+ handlePokemonError(e)
+ emptyList()
+ } finally {
+ _isLoading.value = false
}
- return pokemonListRepository.pokemons(query).map(Pokemon::toUi)
}
override fun navigateToPokemonDetail(pokemonId: Long) {
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/home/HomeActivity.kt b/android/app/src/main/java/poke/rogue/helper/presentation/home/HomeActivity.kt
index 9b31a257..5ef059a6 100644
--- a/android/app/src/main/java/poke/rogue/helper/presentation/home/HomeActivity.kt
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/home/HomeActivity.kt
@@ -13,7 +13,7 @@ import poke.rogue.helper.databinding.ActivityHomeBinding
import poke.rogue.helper.presentation.ability.AbilityActivity
import poke.rogue.helper.presentation.base.toolbar.ToolbarActivity
import poke.rogue.helper.presentation.biome.BiomeActivity
-import poke.rogue.helper.presentation.dex.PokemonActivity
+import poke.rogue.helper.presentation.dex.PokemonListActivity
import poke.rogue.helper.presentation.tip.TipActivity
import poke.rogue.helper.presentation.type.TypeActivity
import poke.rogue.helper.presentation.util.context.startActivity
@@ -52,7 +52,7 @@ class HomeActivity : ToolbarActivity(R.layout.activity_home
}
is HomeNavigateEvent.ToDex ->
- startActivity {
+ startActivity {
logger.logClickEvent(NAVIGATE_TO_DEX)
}
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/util/activity/ActivityExtension.kt b/android/app/src/main/java/poke/rogue/helper/presentation/util/activity/ActivityExtension.kt
new file mode 100644
index 00000000..df929f35
--- /dev/null
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/util/activity/ActivityExtension.kt
@@ -0,0 +1,22 @@
+package poke.rogue.helper.presentation.util.activity
+
+import android.app.Activity
+import android.content.Context
+import android.view.inputmethod.InputMethodManager
+
+fun Activity.hideKeyboard() {
+ val inputMethodManager =
+ getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ currentFocus?.let { view ->
+ inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+ view.clearFocus()
+ }
+}
+
+fun Activity.show() {
+ val inputMethodManager =
+ getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ currentFocus?.let { view ->
+ inputMethodManager.showSoftInput(view, 0)
+ }
+}
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/util/event/EventFlow.kt b/android/app/src/main/java/poke/rogue/helper/presentation/util/event/EventFlow.kt
index 87434b57..0032caed 100644
--- a/android/app/src/main/java/poke/rogue/helper/presentation/util/event/EventFlow.kt
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/util/event/EventFlow.kt
@@ -1,13 +1,9 @@
package poke.rogue.helper.presentation.util.event
import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
import java.util.concurrent.atomic.AtomicBoolean
interface EventFlow : Flow {
@@ -49,33 +45,3 @@ private class EventFlowSlot(val value: T) {
fun markConsumed(): Boolean = consumed.getAndSet(true)
}
-
-fun main() {
- runBlocking {
- val eventFlow = MutableSharedFlow(3)
- eventFlow.emit("Hello")
- eventFlow.emit("Hello2")
- eventFlow.emit("Hello3")
- launch {
- var count = 2
- eventFlow.collect {
- count--
- println(it)
- if (count == 0) cancel()
- }
- }
- launch {
- var count = 2
- eventFlow.collect {
- count--
- println(it)
- if (count == 0) cancel()
- }
- }
- delay(100)
- launch {
- println("Second collector")
- eventFlow.collect { println(it) }
- }
- }
-}
diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/util/event/RefreshEventBus.kt b/android/app/src/main/java/poke/rogue/helper/presentation/util/event/RefreshEventBus.kt
index ec289044..357b1dff 100644
--- a/android/app/src/main/java/poke/rogue/helper/presentation/util/event/RefreshEventBus.kt
+++ b/android/app/src/main/java/poke/rogue/helper/presentation/util/event/RefreshEventBus.kt
@@ -6,10 +6,10 @@ import kotlinx.coroutines.launch
object RefreshEventBus {
private val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob())
- private val _event = MutableEventFlow(capacity = 1)
+ private val _event = MutableEventFlow()
val event: EventFlow = _event.asEventFlow()
- fun send() {
+ fun refresh() {
coroutineScope.launch {
_event.emit(Unit)
}
diff --git a/android/app/src/main/res/layout/activity_pokemon.xml b/android/app/src/main/res/layout/activity_pokemon.xml
deleted file mode 100644
index ef61c706..00000000
--- a/android/app/src/main/res/layout/activity_pokemon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/android/app/src/main/res/layout/fragment_pokemon_list.xml b/android/app/src/main/res/layout/activity_pokemon_list.xml
similarity index 97%
rename from android/app/src/main/res/layout/fragment_pokemon_list.xml
rename to android/app/src/main/res/layout/activity_pokemon_list.xml
index 5fc050fb..d1ce13d3 100644
--- a/android/app/src/main/res/layout/fragment_pokemon_list.xml
+++ b/android/app/src/main/res/layout/activity_pokemon_list.xml
@@ -13,7 +13,7 @@
+ tools:context=".presentation.dex.PokemonListActivity">
()
+ // when
+ eventFlow.emit(1)
+ delay(10)
+ // then
+ eventFlow
+ .onEach {
+ println(">>> onEach: $it")
+ it shouldBe 1
+ }
+ .launchIn(backgroundScope)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun `EventFlow 는 element 를 공유하지 않는다`() =
+ runTest {
+ // given
+ val eventFlow = MutableEventFlow()
+ // when
+ eventFlow.emit(1)
+ delay(10)
+ // then
+ backgroundScope.launch {
+ launch {
+ eventFlow.collect {
+ println(">>> collect: $it")
+ it shouldBe 1
+ }
+ }
+ launch {
+ eventFlow.collect {
+ println(">>> Never Collect AnyThing")
+ it shouldBe Int.MAX_VALUE
+ }
+ }
+ }
+ advanceTimeBy(100)
+ }
+}