Firebase 추가
Crashlytics 추가 RemoteConfig 이용한 강제 업데이트 로직 추가
This commit is contained in:
		
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -20,7 +20,8 @@ bin/ | ||||
| gen/ | ||||
| out/ | ||||
| #  Uncomment the following line in case you need and you don't have the release build type files in your app | ||||
| release/ | ||||
| /release/ | ||||
| /debug/ | ||||
|  | ||||
| # Gradle files | ||||
| .gradle/ | ||||
| @@ -302,7 +303,7 @@ fabric.properties | ||||
| ### AndroidStudio Patch ### | ||||
|  | ||||
| !/gradle/wrapper/gradle-wrapper.jar | ||||
| app/debug/* | ||||
| app/release/* | ||||
| app/debug/ | ||||
| app/release/ | ||||
|  | ||||
| # End of https://www.toptal.com/developers/gitignore/api/macos,android,androidstudio,visualstudiocode,git,kotlin,java | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| plugins { | ||||
|     id 'com.android.application' | ||||
|     id 'org.jetbrains.kotlin.android' | ||||
|     id 'com.google.gms.google-services' | ||||
|     id 'com.google.android.gms.oss-licenses-plugin' | ||||
|  | ||||
|     id 'kotlin-kapt' | ||||
| @@ -8,6 +9,7 @@ plugins { | ||||
|     id 'org.jlleitschuh.gradle.ktlint' | ||||
|  | ||||
|     id 'io.objectbox' | ||||
|     id("com.google.firebase.crashlytics") | ||||
| } | ||||
|  | ||||
| android { | ||||
| @@ -115,4 +117,12 @@ dependencies { | ||||
|     implementation 'com.github.dhaval2404:imagepicker:2.1' | ||||
|  | ||||
|     implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' | ||||
|  | ||||
|     // Firebase | ||||
|     implementation platform('com.google.firebase:firebase-bom:32.2.0') | ||||
|     implementation 'com.google.firebase:firebase-dynamic-links-ktx' | ||||
|     implementation 'com.google.firebase:firebase-crashlytics-ktx' | ||||
|     implementation 'com.google.firebase:firebase-analytics-ktx' | ||||
|     implementation 'com.google.firebase:firebase-messaging-ktx' | ||||
|     implementation 'com.google.firebase:firebase-config-ktx' | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								app/src/debug/google-services.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/src/debug/google-services.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| { | ||||
|   "project_info": { | ||||
|     "project_number": "758414412471", | ||||
|     "project_id": "sodalive-test", | ||||
|     "storage_bucket": "sodalive-test.appspot.com" | ||||
|   }, | ||||
|   "client": [ | ||||
|     { | ||||
|       "client_info": { | ||||
|         "mobilesdk_app_id": "1:758414412471:android:dcea9dff87fa125c7a5b32", | ||||
|         "android_client_info": { | ||||
|           "package_name": "kr.co.vividnext.sodalive.debug" | ||||
|         } | ||||
|       }, | ||||
|       "oauth_client": [ | ||||
|         { | ||||
|           "client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com", | ||||
|           "client_type": 3 | ||||
|         } | ||||
|       ], | ||||
|       "api_key": [ | ||||
|         { | ||||
|           "current_key": "AIzaSyAeNDVDY_r5afz97L1NPvQC6oFy5lPXHNI" | ||||
|         } | ||||
|       ], | ||||
|       "services": { | ||||
|         "appinvite_service": { | ||||
|           "other_platform_oauth_client": [ | ||||
|             { | ||||
|               "client_id": "758414412471-g35socquiplhaamhfl4e6bsta5blabi7.apps.googleusercontent.com", | ||||
|               "client_type": 3 | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   "configuration_version": "1" | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| package kr.co.vividnext.sodalive.base | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.WindowManager | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import kr.co.vividnext.sodalive.databinding.DialogSodaBinding | ||||
| import kr.co.vividnext.sodalive.extensions.dpToPx | ||||
|  | ||||
| open class SodaDialog( | ||||
|     activity: Activity, | ||||
|     layoutInflater: LayoutInflater, | ||||
|     title: String, | ||||
|     desc: String, | ||||
|     confirmButtonTitle: String, | ||||
|     confirmButtonClick: () -> Unit, | ||||
|     cancelButtonTitle: String = "", | ||||
|     cancelButtonClick: (() -> Unit)? = null, | ||||
| ) { | ||||
|  | ||||
|     private val alertDialog: AlertDialog | ||||
|  | ||||
|     val dialogView = DialogSodaBinding.inflate(layoutInflater) | ||||
|  | ||||
|     init { | ||||
|         val dialogBuilder = AlertDialog.Builder(activity) | ||||
|         dialogBuilder.setView(dialogView.root) | ||||
|  | ||||
|         alertDialog = dialogBuilder.create() | ||||
|         alertDialog.setCancelable(false) | ||||
|         alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | ||||
|  | ||||
|         dialogView.tvTitle.text = title | ||||
|         dialogView.tvDesc.text = desc | ||||
|  | ||||
|         dialogView.tvCancel.text = cancelButtonTitle | ||||
|         dialogView.tvCancel.setOnClickListener { | ||||
|             alertDialog.dismiss() | ||||
|             cancelButtonClick?.let { it() } | ||||
|         } | ||||
|  | ||||
|         dialogView.tvConfirm.text = confirmButtonTitle | ||||
|         dialogView.tvConfirm.setOnClickListener { | ||||
|             alertDialog.dismiss() | ||||
|             confirmButtonClick() | ||||
|         } | ||||
|  | ||||
|         dialogView.tvCancel.visibility = if (cancelButtonTitle.isNotBlank()) { | ||||
|             View.VISIBLE | ||||
|         } else { | ||||
|             View.GONE | ||||
|         } | ||||
|  | ||||
|         dialogView.tvConfirm.visibility = if (confirmButtonTitle.isNotBlank()) { | ||||
|             View.VISIBLE | ||||
|         } else { | ||||
|             View.GONE | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun show(width: Int) { | ||||
|         alertDialog.show() | ||||
|  | ||||
|         val lp = WindowManager.LayoutParams() | ||||
|         lp.copyFrom(alertDialog.window?.attributes) | ||||
|         lp.width = width - (26.7f.dpToPx()).toInt() | ||||
|         lp.height = WindowManager.LayoutParams.WRAP_CONTENT | ||||
|  | ||||
|         alertDialog.window?.attributes = lp | ||||
|     } | ||||
| } | ||||
| @@ -12,4 +12,8 @@ object Constants { | ||||
|  | ||||
|     const val EXTRA_DATA = "extra_data" | ||||
|     const val EXTRA_TERMS = "extra_terms" | ||||
|     const val EXTRA_USER_ID = "extra_user_id" | ||||
|     const val EXTRA_ROOM_ID = "extra_room_id" | ||||
|  | ||||
|     const val EXTRA_AUDIO_CONTENT_ID = "extra_audio_content_id" | ||||
| } | ||||
|   | ||||
| @@ -2,10 +2,21 @@ package kr.co.vividnext.sodalive.splash | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.content.Intent | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import androidx.core.os.bundleOf | ||||
| import com.google.firebase.dynamiclinks.PendingDynamicLinkData | ||||
| import com.google.firebase.dynamiclinks.ktx.dynamicLinks | ||||
| import com.google.firebase.ktx.Firebase | ||||
| import com.google.firebase.remoteconfig.ktx.get | ||||
| import com.google.firebase.remoteconfig.ktx.remoteConfig | ||||
| import com.google.firebase.remoteconfig.ktx.remoteConfigSettings | ||||
| import kr.co.vividnext.sodalive.BuildConfig | ||||
| import kr.co.vividnext.sodalive.base.BaseActivity | ||||
| import kr.co.vividnext.sodalive.base.SodaDialog | ||||
| import kr.co.vividnext.sodalive.common.Constants | ||||
| import kr.co.vividnext.sodalive.common.SharedPreferenceManager | ||||
| import kr.co.vividnext.sodalive.databinding.ActivitySplashBinding | ||||
| import kr.co.vividnext.sodalive.main.MainActivity | ||||
| @@ -15,21 +26,163 @@ import kr.co.vividnext.sodalive.user.login.LoginActivity | ||||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding::inflate) { | ||||
|  | ||||
|     private val handler = Handler(Looper.getMainLooper()) | ||||
|     private val remoteConfig = Firebase.remoteConfig | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         if (SharedPreferenceManager.token.isBlank()) { | ||||
|             showLoginActivity() | ||||
|         setupRemoteConfig() | ||||
|         fetchAndroidLatestVersion() | ||||
|     } | ||||
|  | ||||
|     private fun setupRemoteConfig() { | ||||
|         val configSettings = remoteConfigSettings { | ||||
|             minimumFetchIntervalInSeconds = 300 | ||||
|         } | ||||
|         remoteConfig.setConfigSettingsAsync(configSettings) | ||||
|     } | ||||
|  | ||||
|     private fun fetchAndroidLatestVersion() { | ||||
|         remoteConfig.fetchAndActivate() | ||||
|             .addOnCompleteListener(this) { task -> | ||||
|                 if (task.isSuccessful) { | ||||
|                     handler.post { | ||||
|                         checkAppVersion(remoteConfig["android_latest_version"].asString()) | ||||
|                     } | ||||
|                 } else { | ||||
|             showMainActivity() | ||||
|                     checkFirebaseDynamicLink() | ||||
|                 } | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     private fun showMainActivity() { | ||||
|     private fun checkAppVersion(latestVersion: String) { | ||||
|         val versions = BuildConfig.VERSION_NAME.split(".") | ||||
|         val latestVersions = latestVersion.split(".") | ||||
|  | ||||
|         if (latestVersions.isNotEmpty() && latestVersions.size == versions.size) { | ||||
|             val latestMajor = latestVersions[0].toInt() | ||||
|             val latestMinor = latestVersions[1].toInt() | ||||
|             val latestPatch = latestVersions[2].toInt() | ||||
|  | ||||
|             val major = versions[0].toInt() | ||||
|             val minor = versions[1].toInt() | ||||
|             val patch = versions[2].toInt() | ||||
|  | ||||
|             if (latestMajor > major || (latestMajor == major && latestMinor > minor)) { | ||||
|                 showUpdateDialog(isEssential = true) | ||||
|             } else if (latestMajor == major && latestMinor == minor && latestPatch > patch) { | ||||
|                 showUpdateDialog(isEssential = false) | ||||
|             } else { | ||||
|                 checkFirebaseDynamicLink() | ||||
|             } | ||||
|         } else { | ||||
|             checkFirebaseDynamicLink() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun showUpdateDialog(isEssential: Boolean = false) { | ||||
|         val desc = if (isEssential) { | ||||
|             "필수 업데이트가 있습니다.\n업데이트 후 사용가능합니다." | ||||
|         } else { | ||||
|             "최신 업데이트가 있습니다.\n업데이트 하시겠습니까?" | ||||
|         } | ||||
|  | ||||
|         val cancelButtonClick = if (!isEssential) { | ||||
|             { checkFirebaseDynamicLink() } | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         SodaDialog( | ||||
|             activity = this, | ||||
|             layoutInflater = layoutInflater, | ||||
|             title = "업데이트", | ||||
|             desc = desc, | ||||
|             confirmButtonTitle = "업데이트", | ||||
|             confirmButtonClick = { | ||||
|                 startActivity( | ||||
|                     Intent( | ||||
|                         Intent.ACTION_VIEW, | ||||
|                         Uri.parse("market://details?id=${BuildConfig.APPLICATION_ID}") | ||||
|                     ) | ||||
|                 ) | ||||
|                 finish() | ||||
|             }, | ||||
|             cancelButtonTitle = if (isEssential) { | ||||
|                 "" | ||||
|             } else { | ||||
|                 "다음에" | ||||
|             }, | ||||
|             cancelButtonClick = cancelButtonClick | ||||
|         ).show(screenWidth) | ||||
|     } | ||||
|  | ||||
|     private fun checkFirebaseDynamicLink() { | ||||
|         Firebase.dynamicLinks | ||||
|             .getDynamicLink(intent) | ||||
|             .addOnSuccessListener(this) { getDynamicLinkSuccess(it) } | ||||
|             .addOnFailureListener(this) { getDynamicLinkFailure() } | ||||
|     } | ||||
|  | ||||
|     private fun getDynamicLinkSuccess(pendingDynamicLinkData: PendingDynamicLinkData?) { | ||||
|         var deepLink: Uri? = null | ||||
|         if (pendingDynamicLinkData != null) { | ||||
|             deepLink = pendingDynamicLinkData.link | ||||
|         } | ||||
|  | ||||
|         val extras = if (deepLink != null) { | ||||
|             val roomIdString = deepLink.getQueryParameter("room_id") | ||||
|             val channelIdString = deepLink.getQueryParameter("channel_id") | ||||
|             val audioContentIdString = deepLink.getQueryParameter("audio_content_id") | ||||
|  | ||||
|             if (roomIdString != null) { | ||||
|                 bundleOf( | ||||
|                     Constants.EXTRA_ROOM_ID to roomIdString.toLong() | ||||
|                 ) | ||||
|             } else if (channelIdString != null) { | ||||
|                 bundleOf( | ||||
|                     Constants.EXTRA_USER_ID to channelIdString.toLong() | ||||
|                 ) | ||||
|             } else if (audioContentIdString != null) { | ||||
|                 bundleOf( | ||||
|                     Constants.EXTRA_AUDIO_CONTENT_ID to audioContentIdString.toLong() | ||||
|                 ) | ||||
|             } else { | ||||
|                 null | ||||
|             } | ||||
|         } else if (intent.extras != null) { | ||||
|             intent.extras | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         startNextActivity(extras = extras) | ||||
|     } | ||||
|  | ||||
|     private fun getDynamicLinkFailure() { | ||||
|         val extras = intent.getBundleExtra(Constants.EXTRA_DATA) | ||||
|             ?: if (intent.extras != null) { | ||||
|                 intent.extras | ||||
|             } else { | ||||
|                 null | ||||
|             } | ||||
|  | ||||
|         startNextActivity(extras = extras) | ||||
|     } | ||||
|  | ||||
|     private fun startNextActivity(extras: Bundle? = null) { | ||||
|         if (SharedPreferenceManager.token.isBlank()) { | ||||
|             showLoginActivity(extras) | ||||
|         } else { | ||||
|             showMainActivity(extras) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun showMainActivity(extras: Bundle?) { | ||||
|         handler.postDelayed({ | ||||
|             startActivity( | ||||
|                 Intent(applicationContext, MainActivity::class.java).apply { | ||||
|                     putExtra(Constants.EXTRA_DATA, extras) | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||||
|                 } | ||||
| @@ -38,10 +191,11 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(ActivitySplashBinding | ||||
|         }, 500) | ||||
|     } | ||||
|  | ||||
|     private fun showLoginActivity() { | ||||
|     private fun showLoginActivity(extras: Bundle?) { | ||||
|         handler.postDelayed({ | ||||
|             startActivity( | ||||
|                 Intent(applicationContext, LoginActivity::class.java).apply { | ||||
|                     putExtra(Constants.EXTRA_DATA, extras) | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||||
|                     intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) | ||||
|                 } | ||||
|   | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@color/color_339970ff" /> | ||||
|     <corners android:radius="10dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_9970ff" /> | ||||
| </shape> | ||||
							
								
								
									
										79
									
								
								app/src/main/res/layout/dialog_soda.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/src/main/res/layout/dialog_soda.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_gravity="center" | ||||
|     android:background="@drawable/bg_round_corner_10_222222"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_title" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginTop="40dp" | ||||
|         android:fontFamily="@font/gmarket_sans_bold" | ||||
|         android:gravity="center" | ||||
|         android:textColor="@color/color_bbbbbb" | ||||
|         android:textSize="18.3sp" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         tools:text="작성글 등록" /> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_desc" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginTop="6dp" | ||||
|         android:fontFamily="@font/gmarket_sans_medium" | ||||
|         android:gravity="center" | ||||
|         android:textColor="@color/color_bbbbbb" | ||||
|         android:textSize="15sp" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/tv_title" | ||||
|         tools:text="작성한 글을 등록하시겠습니까?" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginHorizontal="16.7dp" | ||||
|         android:layout_marginTop="45dp" | ||||
|         android:layout_marginBottom="16.7dp" | ||||
|         android:gravity="center" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/tv_desc"> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/tv_cancel" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginEnd="13.3dp" | ||||
|             android:layout_weight="1" | ||||
|             android:background="@drawable/bg_round_corner_10_339970ff_9970ff" | ||||
|             android:fontFamily="@font/gmarket_sans_bold" | ||||
|             android:gravity="center" | ||||
|             android:paddingVertical="16dp" | ||||
|             android:textColor="@color/color_9970ff" | ||||
|             android:textSize="18.3sp" | ||||
|             tools:text="취소" /> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/tv_confirm" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_weight="1" | ||||
|             android:background="@drawable/bg_round_corner_10_9970ff" | ||||
|             android:fontFamily="@font/gmarket_sans_bold" | ||||
|             android:gravity="center" | ||||
|             android:paddingVertical="16dp" | ||||
|             android:textColor="@color/white" | ||||
|             android:textSize="18.3sp" | ||||
|             tools:text="확인" /> | ||||
|  | ||||
|     </LinearLayout> | ||||
|  | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
| @@ -16,4 +16,5 @@ | ||||
|  | ||||
|     <color name="color_b3909090">#B3909090</color> | ||||
|     <color name="color_88909090">#88909090</color> | ||||
|     <color name="color_339970ff">#339970FF</color> | ||||
| </resources> | ||||
|   | ||||
| @@ -22,6 +22,8 @@ plugins { | ||||
|     id 'org.jetbrains.kotlin.android' version '1.8.20' apply false | ||||
|  | ||||
|     id "org.jlleitschuh.gradle.ktlint" version "11.2.0" | ||||
|     id 'com.google.gms.google-services' version '4.3.15' apply false | ||||
|     id("com.google.firebase.crashlytics") version "2.9.7" apply false | ||||
| } | ||||
|  | ||||
| task clean(type: Delete) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user