Skip to content

Commit

Permalink
Feat: Implement token storage mechanism and add network request for t…
Browse files Browse the repository at this point in the history
…oken renewal on expiration
  • Loading branch information
gomsang committed Jun 16, 2024
1 parent 65b3d2a commit 5080475
Show file tree
Hide file tree
Showing 33 changed files with 315 additions and 57 deletions.
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".BriefingApplication"
android:allowBackup="true"
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/java/com/dev/briefing/BriefingNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.navigation.compose.NavHost
import store.newsbriefing.app.feature.auth.signInRoute
import store.newsbriefing.app.feature.auth.signInScreen
import store.newsbriefing.app.feature.bookmark.bookmarkScreen
import store.newsbriefing.app.feature.home.homeRoute
import store.newsbriefing.app.feature.home.homeScreen
import store.newsbriefing.app.feature.home.navigateToHome
import store.newsbriefing.app.feature.newsdetail.newsDetailScreen
import store.newsbriefing.app.feature.setting.settingScreen

Expand All @@ -33,6 +33,10 @@ fun BriefingNavHost(
settingScreen(
showSnackbar = appState::showSnackBar
)
signInScreen()
signInScreen(
showSnackBar = appState::showSnackBar,
navigateToHome = {
appState.navController.navigateToHome()
})
}
}
1 change: 1 addition & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ android {
}

dependencies {
implementation(libs.kotlinx.coroutines.android)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package store.newsbriefing.app.core.common.util

import android.util.Log
import store.newsbriefing.app.core.common.BuildConfig

object BriefingLogger {

private fun getClassName(): String {
val ste = Thread.currentThread().stackTrace
for (i in ste.indices) {
if (ste[i].fileName != "BriefingLogger.kt") {
return ste[i].fileName.replace(".kt", "")
}
}
return "UnknownClass"
}

fun e(message: String?, TAG: String = getClassName()) {
if (isDebugMode()) Log.e(TAG, buildLogMsg(message))
}

fun w(message: String?, TAG: String = getClassName()) {
if (isDebugMode()) Log.w(TAG, buildLogMsg(message))
}

fun i(message: String?, TAG: String = getClassName()) {
if (isDebugMode()) Log.i(TAG, buildLogMsg(message))
}

fun d(message: String?, TAG: String = getClassName()) {
if (isDebugMode()) Log.d(TAG, buildLogMsg(message))
}

fun v(message: String?, TAG: String = getClassName()) {
if (isDebugMode()) Log.v(TAG, buildLogMsg(message))
}

private fun isDebugMode(): Boolean = BuildConfig.DEBUG

private fun buildLogMsg(message: String?): String {
val ste = Thread.currentThread().stackTrace[4]
return buildString {
append("[")
append(ste.fileName.replace(".kt", ""))
append("::")
append(ste.methodName)
append("] ")
message?.let { append(it) }
}
}
}
2 changes: 1 addition & 1 deletion core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ dependencies {
api(projects.core.common)
api(projects.core.datastore)

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
implementation(libs.kotlinx.coroutines.android)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package store.newsbriefing.app.core.data.repository

import kotlinx.coroutines.flow.Flow
import store.newsbriefing.app.core.model.MemberToken
import store.newsbriefing.app.core.model.SocialProvider

interface MemberRepository {

suspend fun deleteMember(memberId: Long)
suspend fun getTokenWithSocialProvider(provider: String, identityToken: String): Flow<MemberToken>
suspend fun getTokenWithSocialProvider(provider: SocialProvider, identityToken: String): Flow<MemberToken>
suspend fun getRefreshedAccessToken(refreshToken: String): Flow<MemberToken>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import store.newsbriefing.app.core.data.repository.impl.DefaultBriefingRepositor
import store.newsbriefing.app.core.data.repository.impl.DefaultMemberRepository
import store.newsbriefing.app.core.data.repository.impl.DefaultScrapRepository
import store.newsbriefing.app.core.data.repository.MemberRepository
import store.newsbriefing.app.core.data.repository.MemberTokenRepository
import store.newsbriefing.app.core.data.repository.ScrapRepository
import store.newsbriefing.app.core.data.repository.impl.DefaultMemberTokenRepository

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -28,4 +30,9 @@ abstract class DataModule {
internal abstract fun bindScrapRepository(
repository: DefaultScrapRepository
): ScrapRepository

@Binds
internal abstract fun bindMemberTokenRepository(
repository: DefaultMemberTokenRepository
): MemberTokenRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package store.newsbriefing.app.core.data.repository.impl
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import store.newsbriefing.app.core.data.repository.BriefingRepository
import store.newsbriefing.app.core.data.repository.model.asExternalModel
import store.newsbriefing.app.core.model.BriefingArticle
import store.newsbriefing.app.core.model.BriefingArticleCategory
import store.newsbriefing.app.core.model.BriefingArticleSummary
import store.newsbriefing.app.core.model.TimeOfDay
import store.newsbriefing.app.core.network.datasource.BriefingNetworkDataSource
import store.newsbriefing.app.core.network.model.asExternalModel
import java.time.LocalDate

internal class DefaultBriefingRepository(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import kotlinx.coroutines.flow.flow
import store.newsbriefing.app.core.data.repository.MemberRepository
import store.newsbriefing.app.core.data.repository.model.asExternalModel
import store.newsbriefing.app.core.model.MemberToken
import store.newsbriefing.app.core.model.SocialProvider
import store.newsbriefing.app.core.network.datasource.MemberNetworkDataSource
import javax.inject.Inject

internal class DefaultMemberRepository(private val memberNetworkDataSource: MemberNetworkDataSource) :
internal class DefaultMemberRepository @Inject constructor(private val memberNetworkDataSource: MemberNetworkDataSource) :
MemberRepository {
override suspend fun deleteMember(memberId: Long) {
memberNetworkDataSource.deleteMember(memberId)
}

override suspend fun getTokenWithSocialProvider(
provider: String,
provider: SocialProvider,
identityToken: String
): Flow<MemberToken> = flow {
emit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import kotlinx.coroutines.flow.map
import store.newsbriefing.app.core.data.repository.MemberTokenRepository
import store.newsbriefing.app.core.datastore.datasource.UserAuthTokenDataSource
import store.newsbriefing.app.core.model.MemberToken
import javax.inject.Inject

class DefaultMemberTokenRepository(private val userAuthTokenDataSource: UserAuthTokenDataSource) :
class DefaultMemberTokenRepository @Inject constructor(private val userAuthTokenDataSource: UserAuthTokenDataSource) :
MemberTokenRepository {
override fun getMemberToken(): Flow<MemberToken> {
return userAuthTokenDataSource.getUserAuthToken().map {
Expand Down
2 changes: 0 additions & 2 deletions core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ android {

dependencies {
api(projects.core.model)
api(projects.core.network)
api(projects.core.common)
api(projects.core.datastore)

implementation(libs.datastore.preferences)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package store.newsbriefing.app.core.datastore

object DataStoreConstant {
const val DBNAME = "briefing_datastore.preferences_pb"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.map
import store.newsbriefing.app.core.datastore.model.UserAuthTokenPreferences
import javax.inject.Inject

class DefaultUserAuthTokenDataSource @Inject constructor(val datastore: DataStore<Preferences>) :
internal class DefaultUserAuthTokenDataSource @Inject constructor(private val datastore: DataStore<Preferences>) :
UserAuthTokenDataSource {

private object PreferencesKeys {
Expand All @@ -37,6 +37,12 @@ class DefaultUserAuthTokenDataSource @Inject constructor(val datastore: DataStor
mapUserAuthToken(preferences) ?: throw MissingAuthTokenException()
}

override suspend fun clear() {
datastore.edit { preferences ->
preferences.clear()
}
}

override suspend fun saveUserAuthToken(memberId : Long, accessToken: String, refreshToken: String) {
datastore.edit { preferences ->
preferences[PreferencesKeys.MEMBER_ID] = memberId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import store.newsbriefing.app.core.datastore.model.UserAuthTokenPreferences

interface UserAuthTokenDataSource {
fun getUserAuthToken(): Flow<UserAuthTokenPreferences>
suspend fun clear()
suspend fun saveUserAuthToken(memberId : Long, accessToken: String, refreshToken: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class DataSourceModule {
internal abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindUserAuthTokenDataSource(defaultUserAuthTokenDataSource: DefaultUserAuthTokenDataSource) : UserAuthTokenDataSource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package store.newsbriefing.app.core.datastore.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.dataStoreFile
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import store.newsbriefing.app.core.datastore.DataStoreConstant
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

@Provides
@Singleton
internal fun providesDataStore(@ApplicationContext context: Context): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(produceFile = { context.dataStoreFile(
DataStoreConstant.DBNAME
) })
}
}
4 changes: 4 additions & 0 deletions core/domain/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.briefing.android.library)
id("com.google.devtools.ksp")
}

android {
Expand All @@ -9,4 +10,7 @@ android {
dependencies {
api(projects.core.data)
api(projects.core.model)

implementation(libs.javax.inject)
implementation(libs.kotlinx.coroutines.android)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package store.newsbriefing.app.core.domain

import kotlinx.coroutines.flow.first
import store.newsbriefing.app.core.common.util.BriefingLogger
import store.newsbriefing.app.core.data.repository.MemberRepository
import store.newsbriefing.app.core.data.repository.MemberTokenRepository
import store.newsbriefing.app.core.model.SocialProvider
import javax.inject.Inject

class SignInWithSocialProviderUseCase @Inject constructor(
private val memberRepository: MemberRepository,
private val memberTokenRepository: MemberTokenRepository
) {
suspend operator fun invoke(provider : SocialProvider, identityToken : String) {
val memberToken = memberRepository.getTokenWithSocialProvider(provider, identityToken).first()
memberTokenRepository.saveMemberToken(memberToken.memberId, memberToken.accessToken, memberToken.refreshToken)
BriefingLogger.i("signed in member Id : ${memberToken.memberId}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package store.newsbriefing.app.core.model

enum class SocialProvider(val value: String) {
GOOGLE("google")
}
1 change: 1 addition & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ android {
}

dependencies {
implementation(projects.core.datastore)
api(projects.core.model)
api(projects.core.common)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package store.newsbriefing.app.core.network.datasource

import store.newsbriefing.app.core.model.SocialProvider
import store.newsbriefing.app.core.network.model.NetworkMemberDeleteResponse
import store.newsbriefing.app.core.network.model.NetworkMemberToken


interface MemberNetworkDataSource {
suspend fun deleteMember(memberId: Long)
suspend fun getTokenWithSocialProvider(
provider: String,
provider: SocialProvider,
identityToken: String
): NetworkMemberToken

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ import dagger.hilt.components.SingletonComponent
import store.newsbriefing.app.core.network.datasource.BriefingNetworkDataSource
import store.newsbriefing.app.core.network.datasource.MemberNetworkDataSource
import store.newsbriefing.app.core.network.datasource.ScrapNetworkDataSource
import store.newsbriefing.app.core.network.retrofit.api.RetrofitBriefingNetwork
import store.newsbriefing.app.core.network.retrofit.api.RetrofitMemberNetwork
import store.newsbriefing.app.core.network.retrofit.api.RetrofitScrapNetwork
import store.newsbriefing.app.core.network.retrofit.api.RetrofitBriefingNetworkDataSource
import store.newsbriefing.app.core.network.retrofit.api.RetrofitMemberNetworkDataSource
import store.newsbriefing.app.core.network.retrofit.api.RetrofitScrapNetworkDataSource

@Module
@InstallIn(SingletonComponent::class)
internal interface NetworkModule {
@Binds
fun bindBriefingNetworkDataSource(
retrofitBriefingNetwork: RetrofitBriefingNetwork
retrofitBriefingNetworkDataSource: RetrofitBriefingNetworkDataSource
): BriefingNetworkDataSource

@Binds
fun bindMemberNetworkDataSource(
retrofitMemberNetwork: RetrofitMemberNetwork
retrofitMemberNetworkDataSource: RetrofitMemberNetworkDataSource
): MemberNetworkDataSource

@Binds
fun bindScrapNetworkDataSource(
retrofitScrapNetwork: RetrofitScrapNetwork
retrofitScrapNetworkDataSource: RetrofitScrapNetworkDataSource
): ScrapNetworkDataSource
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import store.newsbriefing.app.core.common.BuildConfig
import store.newsbriefing.app.core.network.util.AuthInterceptor
import javax.inject.Singleton

@Module
Expand All @@ -26,8 +27,10 @@ object RetrofitModule {

@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().build()
fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}

@Provides
Expand Down
Loading

0 comments on commit 5080475

Please sign in to comment.