Firebase 추가

Crashlytics 추가
RemoteConfig 이용한 강제 업데이트 로직 추가
This commit is contained in:
klaus 2023-07-25 02:40:41 +09:00
parent edbaceba0b
commit fd8c4e726d
10 changed files with 380 additions and 8 deletions

7
.gitignore vendored
View File

@ -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

View File

@ -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'
}

View 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"
}

View File

@ -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
}
}

View File

@ -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"
}

View File

@ -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 {
checkFirebaseDynamicLink()
}
}
}
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 {
showMainActivity()
checkFirebaseDynamicLink()
}
}
private fun showMainActivity() {
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)
}

View File

@ -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>

View 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>

View File

@ -16,4 +16,5 @@
<color name="color_b3909090">#B3909090</color>
<color name="color_88909090">#88909090</color>
<color name="color_339970ff">#339970FF</color>
</resources>

View File

@ -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) {