Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.dagger.hilt)
id("org.jetbrains.kotlin.kapt")
alias(libs.plugins.navigationSafeArgs)
id("com.google.gms.google-services")
alias(libs.plugins.googleServices)
alias(libs.plugins.firebaseCrashlytics)
}

val properties = Properties().apply {
Expand Down Expand Up @@ -77,7 +79,6 @@ android {
kotlinOptions {
jvmTarget = "11"
}
composeOptions { kotlinCompilerExtensionVersion = "1.5.14" }
}

dependencies {
Expand Down Expand Up @@ -163,7 +164,8 @@ dependencies {
implementation(libs.accompanist.permissions)

// Firebase
implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-config-ktx")
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.config.ktx)
implementation(libs.firebase.crashlytics)
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.kuit.findu.analytics

data class AnalyticsEvent(
val type: String,
val extras: List<Param> = emptyList(),
) {
data class Param(
val key: String,
val value: String,
)

companion object {
const val SCREEN_VIEW = "screen_view" // TYPE
const val SCREEN_NAME = "screen_name" // EXTRA_KEY
}
}
36 changes: 36 additions & 0 deletions app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.kuit.findu.analytics

fun AnalyticsHelper.logScreenView(screenName: String) {
logEvent(
AnalyticsEvent(
type = AnalyticsEvent.SCREEN_VIEW,
extras = listOf(
AnalyticsEvent.Param(AnalyticsEvent.SCREEN_NAME, screenName),
),
),
)
}

fun AnalyticsHelper.logUserSignIn(userName: String, type: String) {
logEvent(
AnalyticsEvent(
type = "signed_in",
extras = listOf(
AnalyticsEvent.Param("user_name", userName),
AnalyticsEvent.Param("login_type", type),
),
),
)
}

fun AnalyticsHelper.logUserSignUp(userName: String) {
logEvent(
AnalyticsEvent(
type = "signed_in",
extras = listOf(
AnalyticsEvent.Param("user_name", userName),
AnalyticsEvent.Param("login_type", "Kakao"),
),
),
)
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/kuit/findu/analytics/AnalyticsHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.kuit.findu.analytics

interface AnalyticsHelper {
fun logEvent(event: AnalyticsEvent)
}
21 changes: 21 additions & 0 deletions app/src/main/java/com/kuit/findu/analytics/AnalyticsHelperImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.kuit.findu.analytics

import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.logEvent
import javax.inject.Inject

class AnalyticsHelperImpl @Inject constructor(
private val firebaseAnalytics: FirebaseAnalytics,
) : AnalyticsHelper {
override fun logEvent(event: AnalyticsEvent) {
firebaseAnalytics.logEvent(event.type) {
for (extra in event.extras) {
// Key, Value Max Length에 따른 slicing
param(
key = extra.key.take(40),
value = extra.value.take(100),
)
}
}
}
}
37 changes: 37 additions & 0 deletions app/src/main/java/com/kuit/findu/analytics/di/FirebaseModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.kuit.findu.analytics.di

import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.crashlytics
import com.kuit.findu.analytics.AnalyticsHelper
import com.kuit.findu.analytics.AnalyticsHelperImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class FirebaseModule {
@Binds
@Singleton
abstract fun bindsAnalyticsHelper(analyticsHelperImpl: AnalyticsHelperImpl): AnalyticsHelper

companion object {
@Provides
@Singleton
fun provideFirebaseAnalytics(): FirebaseAnalytics {
return Firebase.analytics
}

@Provides
@Singleton
fun provideFirebaseCrashlytics(): FirebaseCrashlytics {
return Firebase.crashlytics
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.kuit.findu.data.dataremote.util

import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.recordException
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject

class ErrorTrackingInterceptor @Inject constructor(
private val firebaseCrashlytics: FirebaseCrashlytics,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response: Response

try {
response = chain.proceed(request)
} catch (e: Exception) {
// 1. 네트워크 자체가 끊긴 경우 등 (IOException)
firebaseCrashlytics.recordException(e)
throw e
}

// 2. 서버에서 응답은 왔으나 4xx, 5xx 에러인 경우
if (!response.isSuccessful) {
val code = response.code

val url = request.url.toString()

// Crashlytics에 상세 정보 기록
val exceptionMessage = when (response.code) {
in (400..499) -> "Client $code Error"
in (500..599) -> "Server $code Error"
else -> "Unknown $code Error"
}
firebaseCrashlytics.recordException(Exception(exceptionMessage)) {
key("api_method", request.method)
key("api_url", url)
key("api_status", code)
}
}

return response
}
}
20 changes: 16 additions & 4 deletions app/src/main/java/com/kuit/findu/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.kuit.findu.di

import android.content.Context
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.kuit.findu.BuildConfig
import com.kuit.findu.BuildConfig.DEBUG
import com.kuit.findu.data.datalocal.datasource.TokenLocalDataSource
import com.kuit.findu.data.dataremote.util.AuthInterceptor
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.kuit.findu.data.dataremote.util.ErrorTrackingInterceptor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -39,13 +41,15 @@ object NetworkModule {
@Singleton
fun providesOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
authInterceptor: AuthInterceptor
authInterceptor: AuthInterceptor,
errorTrackingInterceptor: ErrorTrackingInterceptor,
): OkHttpClient =
OkHttpClient.Builder().apply {
connectTimeout(10, TimeUnit.SECONDS)
writeTimeout(10, TimeUnit.SECONDS)
readTimeout(10, TimeUnit.SECONDS)
if (DEBUG) addInterceptor(loggingInterceptor)
else addInterceptor(errorTrackingInterceptor)
addInterceptor(authInterceptor)
}.build()

Expand All @@ -60,17 +64,25 @@ object NetworkModule {
@Singleton
fun provideAuthInterceptor(
tokenLocalDataSource: TokenLocalDataSource,
@ApplicationContext context: Context
@ApplicationContext context: Context,
): AuthInterceptor {
return AuthInterceptor(tokenLocalDataSource, context)
}

@Provides
@Singleton
fun provideErrorTrackingInterceptor(
firebaseCrashlytics: FirebaseCrashlytics,
): ErrorTrackingInterceptor {
return ErrorTrackingInterceptor(firebaseCrashlytics)
}

@ExperimentalSerializationApi
@Provides
@Singleton
fun providesRetrofit(
okHttpClient: OkHttpClient,
json: Json
json: Json,
): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
Expand Down Expand Up @@ -76,11 +76,22 @@ class HomeFragment : Fragment() {
}

is HomeUiEffect.ShowToast -> {
Toast.makeText(requireContext(), sideEffect.message, Toast.LENGTH_SHORT).show()
Toast.makeText(
requireContext(),
sideEffect.message,
Toast.LENGTH_SHORT
).show()
}

is HomeUiEffect.NavigateToProtectList -> TODO()
is HomeUiEffect.NavigateToReportList -> TODO()
is HomeUiEffect.NavigateToProtectList -> {}
is HomeUiEffect.NavigateToReportList -> {}

is HomeUiEffect.NavigateToFindReport -> {
navigateToFindReport()
}
is HomeUiEffect.NavigateToLostReport -> {
navigateToLostReport()
}

is HomeUiEffect.Dial -> call120()
}
Expand Down Expand Up @@ -110,7 +121,11 @@ class HomeFragment : Fragment() {
homeViewModel.handleEvent(HomeUiEvent.OnAlarmButtonClick)
},
onIndicatorSelected = { reportDurationType ->
homeViewModel.handleEvent(HomeUiEvent.OnHomeReportDurationClick(reportDurationType))
homeViewModel.handleEvent(
HomeUiEvent.OnHomeReportDurationClick(
reportDurationType
)
)
},
userNickname = uiState.nickname,
navigateToProtectDetail = { protectAnimal ->
Expand All @@ -128,13 +143,18 @@ class HomeFragment : Fragment() {
onReportDialogDismiss = {
homeViewModel.handleEvent(HomeUiEvent.OnReportDialogDismiss)
},
onLostReportClick = {},
onFindReportClick = {},
onLostReportClick = {
homeViewModel.navigateToLostReport()
},
onFindReportClick = {
homeViewModel.navigateToFindReport()
},
onPhoneClicked = {
homeViewModel.dial()
},
navigateToHomeExtra = { homeExtraButtonType->
navigateToHomeExtra(homeExtraButtonType) },
navigateToHomeExtra = { homeExtraButtonType ->
navigateToHomeExtra(homeExtraButtonType)
},
)
}

Expand Down Expand Up @@ -192,6 +212,18 @@ class HomeFragment : Fragment() {
}
}

private fun navigateToLostReport() {
findNavController().navigate(
HomeFragmentDirections.actionFragmentHomeToFragmentMissingReport()
)
}

private fun navigateToFindReport() {
findNavController().navigate(
HomeFragmentDirections.actionFragmentHomeToFragmentWitnessReport()
)
}

private fun openWebLink(url: String) {
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
requireActivity().startActivity(intent)
Expand Down
Loading