Skip to content

Commit da9a653

Browse files
authored
feat: Remote Config 적용 (#22)
* feat: FireStoreRepository 구현 * feat: RemoteConfigRepository 구현 * feat: 피드백 URL 조회 로직 구현 & RemoteConfigRepository 싱글톤 적용 * refactor: FireStoreRepository 싱글톤 적용 * fix: 개발 환경에서의 restaurant 콜렉션 변경(배포 환경과 동일) * fix: fetch 인터벌 값 상수화 * fix: import 미삭제 코드 제거
1 parent 288cf83 commit da9a653

File tree

7 files changed

+200
-82
lines changed

7 files changed

+200
-82
lines changed

app/src/main/java/com/woozoo/menumonya/Application.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.woozoo.menumonya
22

33
import android.app.Application
44
import android.content.Context
5+
import com.woozoo.menumonya.repository.FireStoreRepository
6+
import com.woozoo.menumonya.repository.RemoteConfigRepository
57

68
class Application: Application() {
79

@@ -16,4 +18,12 @@ class Application: Application() {
1618
return instance!!.applicationContext
1719
}
1820
}
21+
22+
override fun onCreate() {
23+
super.onCreate()
24+
25+
// 종속 관계 때문에 초기화 순서 중요함
26+
RemoteConfigRepository.initialize()
27+
FireStoreRepository.initialize()
28+
}
1929
}

app/src/main/java/com/woozoo/menumonya/Constants.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ class Constants {
1212
val GLIDE_IMAGE_SIZE_WIDTH = 100
1313
val GLIDE_IMAGE_SIZE_HEIGHT = 100
1414

15-
val FEEDBACK_URL = "https://forms.gle/Emcodxf3ngNqLCHs7"
15+
val REMOTE_CONFIG_FETCH_INTERVAL: Long = 60 * 1 // 1분마다 업데이트 함
1616
}
1717
}

app/src/main/java/com/woozoo/menumonya/MainActivity.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.appcompat.app.AppCompatActivity
1515
import androidx.recyclerview.widget.RecyclerView
1616
import androidx.viewpager2.widget.ViewPager2
1717
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
18-
import com.woozoo.menumonya.Constants.Companion.FEEDBACK_URL
1918
import com.woozoo.menumonya.MainViewModel.Event
2019
import com.woozoo.menumonya.databinding.ActivityMainBinding
2120
import com.woozoo.menumonya.util.PermissionUtils.Companion.ACCESS_FINE_LOCATION_REQUEST_CODE
@@ -86,17 +85,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
8685

8786
binding.naverMap.onCreate(savedInstanceState)
8887

89-
viewModel.initializeMapView(binding.naverMap, this)
90-
}
91-
92-
private fun updatePagerHeightForChild(view: View, pager: ViewPager2) {
93-
view.post {
94-
val wMeasureSpec = View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
95-
val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
96-
view.measure(wMeasureSpec, hMeasureSpec)
97-
pager.layoutParams = (pager.layoutParams).also { lp -> lp.height = view.measuredHeight }
98-
pager.invalidate()
99-
}
88+
viewModel.initializeMapView(binding.naverMap)
10089
}
10190

10291
private fun handleEvent(event: Event) = when (event) {
@@ -210,7 +199,8 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
210199
binding.currentLocationIv.setColorFilter(resources.getColor(R.color.colorPrimary))
211200
}
212201
R.id.feedback_iv -> {
213-
val intent = Intent(ACTION_VIEW, Uri.parse(FEEDBACK_URL))
202+
val feedbackUrl = viewModel.getFeedbackUrl()
203+
val intent = Intent(ACTION_VIEW, Uri.parse(feedbackUrl))
214204
startActivity(intent)
215205
}
216206
// '내 주변' 버튼 클릭

app/src/main/java/com/woozoo/menumonya/MainViewModel.kt

Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ import android.location.LocationListener
88
import android.location.LocationManager
99
import androidx.lifecycle.AndroidViewModel
1010
import androidx.lifecycle.viewModelScope
11-
import com.google.firebase.firestore.ktx.firestore
12-
import com.google.firebase.firestore.ktx.toObject
13-
import com.google.firebase.ktx.Firebase
1411
import com.naver.maps.geometry.LatLng
1512
import com.naver.maps.map.*
1613
import com.naver.maps.map.overlay.Marker
@@ -21,19 +18,15 @@ import com.woozoo.menumonya.Constants.Companion.LATLNG_GN
2118
import com.woozoo.menumonya.Constants.Companion.LATLNG_YS
2219
import com.woozoo.menumonya.Constants.Companion.MAP_DEFAULT_ZOOM
2320
import com.woozoo.menumonya.Constants.Companion.MAP_MIN_ZOOM
24-
import com.woozoo.menumonya.model.Menu
2521
import com.woozoo.menumonya.model.Restaurant
26-
import com.woozoo.menumonya.util.DateUtils.Companion.getTodayDate
22+
import com.woozoo.menumonya.repository.FireStoreRepository
23+
import com.woozoo.menumonya.repository.RemoteConfigRepository
2724
import com.woozoo.menumonya.util.LocationUtils.Companion.requestLocationUpdateOnce
2825
import com.woozoo.menumonya.util.PermissionUtils.Companion.isGpsPermissionAllowed
29-
3026
import com.woozoo.menumonya.util.PermissionUtils.Companion.isLocationPermissionAllowed
31-
import kotlinx.coroutines.Deferred
32-
import kotlinx.coroutines.async
3327
import kotlinx.coroutines.flow.MutableSharedFlow
3428
import kotlinx.coroutines.flow.asSharedFlow
3529
import kotlinx.coroutines.launch
36-
import kotlinx.coroutines.tasks.await
3730
import java.lang.Double.parseDouble
3831

3932
class MainViewModel(application: Application): AndroidViewModel(Application()) {
@@ -45,6 +38,9 @@ class MainViewModel(application: Application): AndroidViewModel(Application()) {
4538
lateinit var naverMap: NaverMap
4639
private var locationManager: LocationManager
4740

41+
private var firestoreRepository = FireStoreRepository.get()
42+
private var remoteConfigRepository = RemoteConfigRepository.get()
43+
4844
private var mRestaurantInfoArray: ArrayList<Restaurant> = ArrayList()
4945
private var markerList: ArrayList<Marker> = ArrayList()
5046
private var selectedLocation: String = ""
@@ -60,13 +56,9 @@ class MainViewModel(application: Application): AndroidViewModel(Application()) {
6056
}
6157

6258
@SuppressLint("MissingPermission")
63-
fun initializeMapView(mapView: MapView, activity: Activity) {
59+
fun initializeMapView(mapView: MapView) {
6460
mapView.getMapAsync {
6561
naverMap = it.apply {
66-
// locationSource = FusedLocationSource(
67-
// activity,
68-
// LOCATION_PERMISSION_REQUEST_CODE
69-
// )
7062
locationTrackingMode = LocationTrackingMode.NoFollow
7163
uiSettings.apply {
7264
isLocationButtonEnabled = false
@@ -122,59 +114,6 @@ class MainViewModel(application: Application): AndroidViewModel(Application()) {
122114
naverMap.moveCamera(CameraUpdate.withParams(cameraUpdateParams))
123115
}
124116

125-
suspend fun getTodayRestaurantInfoAsync(location: String): Deferred<ArrayList<Restaurant>> {
126-
return viewModelScope.async {
127-
val restaurantInfo = ArrayList<Restaurant>()
128-
val db = Firebase.firestore
129-
val restaurantRef = db.collection("restaurants")
130-
val query = restaurantRef.whereArrayContainsAny("locationCategory", listOf(location))
131-
132-
val result = query.get().await()
133-
val documents = result.documents
134-
135-
for (document in documents) {
136-
val restaurant = document.toObject<Restaurant>()
137-
138-
if (restaurant != null) {
139-
// 메뉴 정보 조회
140-
val menu = getMenuAsync(document.id)?.await()
141-
142-
val todayMenu = menu?.date?.get(getTodayDate())
143-
if (todayMenu != null) restaurant.todayMenu = todayMenu
144-
145-
restaurantInfo.add(restaurant)
146-
}
147-
}
148-
149-
// locationCategoryOrder값으로 순서 재정렬(가까운 블록에 위치한 순서대로)
150-
for (restaurant in restaurantInfo) {
151-
restaurant.locationCategoryOrder.removeAll { !it.contains(location) }
152-
}
153-
restaurantInfo.sortBy { it.locationCategoryOrder[0] }
154-
155-
restaurantInfo
156-
}
157-
}
158-
159-
suspend fun getMenuAsync(restaurantId: String): Deferred<Menu>? {
160-
return viewModelScope.async {
161-
var menu = Menu()
162-
163-
val db = Firebase.firestore
164-
val menuRef = db.collection("menus")
165-
val query = menuRef.whereEqualTo("restaurantId", restaurantId)
166-
167-
val result = query.get().await()
168-
val documents = result.documents
169-
170-
if (documents.size > 0) {
171-
menu = documents[0].toObject<Menu>()!!
172-
}
173-
174-
menu
175-
}
176-
}
177-
178117
/**
179118
* 하단의 식당 정보 가로 스크롤 뷰를 표시함.
180119
* - (중요) 지도에 마커를 표시하기 위한 식당 정보를 이미 fetch하였다는 전제 하에 작동함.
@@ -194,7 +133,7 @@ class MainViewModel(application: Application): AndroidViewModel(Application()) {
194133
"역삼" -> moveCameraCoord(LATLNG_YS.latitude, LATLNG_YS.longitude)
195134
}
196135

197-
mRestaurantInfoArray = getTodayRestaurantInfoAsync(selectedLocation).await() // TODO: 정렬 안돼있음
136+
mRestaurantInfoArray = firestoreRepository.getRestaurantInLocation(location)
198137

199138
setMarkers(mRestaurantInfoArray)
200139
}
@@ -263,6 +202,10 @@ class MainViewModel(application: Application): AndroidViewModel(Application()) {
263202
}
264203
}
265204

205+
fun getFeedbackUrl(): String {
206+
return remoteConfigRepository.getFeedbackUrl()
207+
}
208+
266209
private fun showToast(text: String) {
267210
event(Event.ShowToast(text))
268211
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.woozoo.menumonya.repository
2+
3+
import com.google.firebase.firestore.ktx.firestore
4+
import com.google.firebase.firestore.ktx.toObject
5+
import com.google.firebase.ktx.Firebase
6+
import com.woozoo.menumonya.model.Menu
7+
import com.woozoo.menumonya.model.Restaurant
8+
import com.woozoo.menumonya.util.DateUtils
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.tasks.await
11+
import kotlinx.coroutines.withContext
12+
13+
class FireStoreRepository {
14+
private val db = Firebase.firestore
15+
private var remoteConfigRepository = RemoteConfigRepository.get()
16+
17+
private lateinit var restaurantCollectionName: String
18+
private lateinit var menuCollectionName: String
19+
20+
companion object {
21+
private var instance: FireStoreRepository? = null
22+
23+
fun initialize() {
24+
if (instance == null) {
25+
instance = FireStoreRepository()
26+
}
27+
}
28+
29+
fun get(): FireStoreRepository {
30+
return instance ?: throw java.lang.IllegalStateException("FireStoreRepository must be initialized")
31+
}
32+
}
33+
34+
suspend fun getRestaurantInLocation(location: String) = withContext(Dispatchers.IO) {
35+
restaurantCollectionName = remoteConfigRepository.getRestaurantsCollectionName()
36+
37+
val restaurantInfo = ArrayList<Restaurant>()
38+
val restaurantRef = db.collection(restaurantCollectionName)
39+
val query = restaurantRef.whereArrayContainsAny("locationCategory", listOf(location))
40+
41+
val result = query.get().await()
42+
val documents = result.documents
43+
44+
for (document in documents) {
45+
val restaurant = document.toObject<Restaurant>()
46+
47+
if (restaurant != null) {
48+
// 메뉴 정보 조회
49+
val menu = getMenu(document.id)
50+
51+
val todayMenu = menu.date.get(DateUtils.getTodayDate())
52+
if (todayMenu != null) restaurant.todayMenu = todayMenu
53+
54+
restaurantInfo.add(restaurant)
55+
}
56+
}
57+
58+
// locationCategoryOrder값으로 순서 재정렬(가까운 블록에 위치한 순서대로)
59+
for (restaurant in restaurantInfo) {
60+
restaurant.locationCategoryOrder.removeAll { !it.contains(location) }
61+
}
62+
restaurantInfo.sortBy { it.locationCategoryOrder[0] }
63+
64+
restaurantInfo
65+
}
66+
67+
suspend fun getMenu(restaurantId: String) = withContext(Dispatchers.IO) {
68+
menuCollectionName = remoteConfigRepository.getMenuCollectionName()
69+
70+
var menu = Menu()
71+
72+
val menuRef = db.collection(menuCollectionName)
73+
val query = menuRef.whereEqualTo("restaurantId", restaurantId)
74+
75+
val result = query.get().await()
76+
val documents = result.documents
77+
78+
if (documents.size > 0) {
79+
menu = documents[0].toObject<Menu>()!!
80+
}
81+
82+
menu
83+
}
84+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.woozoo.menumonya.repository
2+
3+
import android.util.Log
4+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
5+
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
6+
import com.woozoo.menumonya.BuildConfig
7+
import com.woozoo.menumonya.Constants.Companion.REMOTE_CONFIG_FETCH_INTERVAL
8+
import com.woozoo.menumonya.R
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.withContext
11+
12+
class RemoteConfigRepository {
13+
private val remoteConfig = FirebaseRemoteConfig.getInstance()
14+
15+
companion object {
16+
private var instance: RemoteConfigRepository? = null
17+
18+
fun initialize() {
19+
if (instance == null) {
20+
instance = RemoteConfigRepository()
21+
}
22+
}
23+
24+
fun get(): RemoteConfigRepository {
25+
return instance ?: throw java.lang.IllegalStateException("RemoteConfigRepository must be initialized")
26+
}
27+
}
28+
29+
private val configSettings = remoteConfigSettings {
30+
minimumFetchIntervalInSeconds = REMOTE_CONFIG_FETCH_INTERVAL
31+
}
32+
33+
init {
34+
remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
35+
remoteConfig.setConfigSettingsAsync(configSettings)
36+
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
37+
if (task.isSuccessful) {
38+
Log.d("zzanzu", "remote config fetch successful")
39+
} else {
40+
Log.d("zzanzu", "remote config fetch failure")
41+
}
42+
}
43+
}
44+
45+
suspend fun getRestaurantsCollectionName() = withContext(Dispatchers.IO) {
46+
if (BuildConfig.DEBUG) {
47+
remoteConfig.getString("RESTAURANT_COLLECTION_DEV")
48+
} else {
49+
remoteConfig.getString("RESTAURANT_COLLECTION_PROD")
50+
}
51+
}
52+
53+
suspend fun getMenuCollectionName() = withContext(Dispatchers.IO) {
54+
if (BuildConfig.DEBUG) {
55+
remoteConfig.getString("MENU_COLLECTION_DEV")
56+
} else {
57+
remoteConfig.getString("MENU_COLLECTION_PROD")
58+
}
59+
}
60+
61+
fun getFeedbackUrl(): String {
62+
return if (BuildConfig.DEBUG) {
63+
remoteConfig.getString("FEEDBACK_URL_DEV")
64+
} else {
65+
remoteConfig.getString("FEEDBACK_URL_PROD")
66+
}
67+
}
68+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<defaults>
3+
<entry>
4+
<key>FEEDBACK_URL</key>
5+
<value>https://bit.ly/3oDijQp</value>
6+
</entry>
7+
<entry>
8+
<key>MENU_COLLECTION_DEV</key>
9+
<value>menus-dev</value>
10+
</entry>
11+
<entry>
12+
<key>MENU_COLLECTION_PROD</key>
13+
<value>menus</value>
14+
</entry>
15+
<entry>
16+
<key>RESTAURANT_COLLECTION_DEV</key>
17+
<value>restaurants</value>
18+
</entry>
19+
<entry>
20+
<key>RESTAURANT_COLLECTION_PROD</key>
21+
<value>restaurants</value>
22+
</entry>
23+
</defaults>

0 commit comments

Comments
 (0)