From fb92a69faee80e0f5f4cf1c92b4ded697c93017b Mon Sep 17 00:00:00 2001 From: erik-everardo Date: Sat, 27 Jan 2024 00:33:58 -0600 Subject: [PATCH] guarda info basica de perfiles para uso offline. Por ahora, se utiliza la info del usuario actual para mostrarla en ajustes --- .../main/java/com/isolaatti/auth/Module.kt | 10 +++++-- .../isolaatti/auth/data/AuthRepositoryImpl.kt | 30 ++++++++++++++++--- .../auth/data/UserInfoRepositoryImpl.kt | 22 ++++++++++++++ .../isolaatti/auth/data/local/UserInfoDao.kt | 16 ++++++++++ .../auth/data/local/UserInfoEntity.kt | 11 +++++++ .../isolaatti/auth/domain/AuthRepository.kt | 1 + .../com/isolaatti/auth/domain/UserInfo.kt | 15 ++++++++++ .../auth/domain/UserInfoRepository.kt | 7 +++++ .../BottomSheetPostOptionsViewModel.kt | 28 +++++++++-------- .../com/isolaatti/database/AppDatabase.kt | 5 +++- .../java/com/isolaatti/database/Module.kt | 4 ++- .../main/java/com/isolaatti/profile/Module.kt | 13 ++++++-- .../data/repository/ProfileRepositoryImpl.kt | 8 ++++- .../isolaatti/settings/data/KeyValueDao.kt | 6 +++- .../settings/data/SettingsRepositoryImpl.kt | 7 ++++- .../settings/domain/SettingsRepository.kt | 6 +++- .../settings/domain/UserIdSetting.kt | 9 ++++-- .../presentation/SettingsViewModel.kt | 26 ++++++++++++++++ .../isolaatti/settings/ui/SettingsFragment.kt | 16 ++++++++++ 19 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/isolaatti/auth/data/UserInfoRepositoryImpl.kt create mode 100644 app/src/main/java/com/isolaatti/auth/data/local/UserInfoDao.kt create mode 100644 app/src/main/java/com/isolaatti/auth/data/local/UserInfoEntity.kt create mode 100644 app/src/main/java/com/isolaatti/auth/domain/UserInfo.kt create mode 100644 app/src/main/java/com/isolaatti/auth/domain/UserInfoRepository.kt create mode 100644 app/src/main/java/com/isolaatti/settings/presentation/SettingsViewModel.kt diff --git a/app/src/main/java/com/isolaatti/auth/Module.kt b/app/src/main/java/com/isolaatti/auth/Module.kt index 80573cb..ebf5494 100644 --- a/app/src/main/java/com/isolaatti/auth/Module.kt +++ b/app/src/main/java/com/isolaatti/auth/Module.kt @@ -1,12 +1,13 @@ package com.isolaatti.auth import com.isolaatti.auth.data.AuthRepositoryImpl -import com.isolaatti.settings.data.KeyValueDao +import com.isolaatti.auth.data.UserInfoRepositoryImpl import com.isolaatti.auth.data.local.TokenStorage +import com.isolaatti.auth.data.local.UserInfoDao import com.isolaatti.auth.data.remote.AuthApi import com.isolaatti.auth.domain.AuthRepository +import com.isolaatti.auth.domain.UserInfoRepository import com.isolaatti.connectivity.RetrofitClient -import com.isolaatti.database.AppDatabase import com.isolaatti.settings.domain.UserIdSetting import dagger.Module import dagger.Provides @@ -25,4 +26,9 @@ class Module { fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, userIdSetting: UserIdSetting): AuthRepository { return AuthRepositoryImpl(tokenStorage, authApi, userIdSetting) } + + @Provides + fun provideUserInfoRepository(userIdSetting: UserIdSetting, userInfoDao: UserInfoDao): UserInfoRepository { + return UserInfoRepositoryImpl(userIdSetting, userInfoDao) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt index 723fffa..7d4b1b6 100644 --- a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt @@ -1,8 +1,6 @@ package com.isolaatti.auth.data import com.isolaatti.BuildConfig -import com.isolaatti.settings.data.KeyValueDao -import com.isolaatti.settings.data.KeyValueEntity import com.isolaatti.auth.data.remote.AuthTokenDto import com.isolaatti.auth.data.local.TokenStorage import com.isolaatti.auth.data.remote.AuthApi @@ -10,8 +8,12 @@ import com.isolaatti.auth.data.remote.Credential import com.isolaatti.auth.domain.AuthRepository import com.isolaatti.settings.domain.UserIdSetting import com.isolaatti.utils.Resource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.last +import kotlinx.coroutines.launch import retrofit2.awaitResponse import javax.inject.Inject @@ -51,8 +53,15 @@ class AuthRepositoryImpl @Inject constructor( return tokenStorage.token } - override fun getUserId(): Flow = flow { - emit(userIdSetting.getUserId()) + override fun getUserId(): Flow { + CoroutineScope(Dispatchers.IO).launch { + val currentUserId = userIdSetting.getUserIdAsync() + if(currentUserId == null) { + validateSession() + } + } + + return userIdSetting.getUserId() } override suspend fun setToken(sessionDto: AuthTokenDto) { @@ -60,4 +69,17 @@ class AuthRepositoryImpl @Inject constructor( userIdSetting.setUserId(sessionDto.userId) } + private suspend fun validateSession() { + val token = tokenStorage.token?.token + + if(token != null) { + try { + val response = authApi.validateTokenUrl(token).awaitResponse() + if(response.isSuccessful) { + response.body()?.userId?.let { userIdSetting.setUserId(it) } + } + } catch(_: Exception) { } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/UserInfoRepositoryImpl.kt b/app/src/main/java/com/isolaatti/auth/data/UserInfoRepositoryImpl.kt new file mode 100644 index 0000000..977a1a7 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/data/UserInfoRepositoryImpl.kt @@ -0,0 +1,22 @@ +package com.isolaatti.auth.data + +import com.isolaatti.auth.data.local.UserInfoDao +import com.isolaatti.auth.domain.UserInfo +import com.isolaatti.auth.domain.UserInfoRepository +import com.isolaatti.settings.data.KeyValueDao +import com.isolaatti.settings.domain.UserIdSetting +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class UserInfoRepositoryImpl @Inject constructor(private val userIdSetting: UserIdSetting, private val userInfoDao: UserInfoDao) : UserInfoRepository { + override fun getCurrentUserInfo(): Flow = flow { + val currentUserId = userIdSetting.getUserIdAsync() + if (currentUserId != null) { + emitAll(userInfoDao.getUserInfo(currentUserId).map { UserInfo.fromEntity(it) }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/local/UserInfoDao.kt b/app/src/main/java/com/isolaatti/auth/data/local/UserInfoDao.kt new file mode 100644 index 0000000..97bed29 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/data/local/UserInfoDao.kt @@ -0,0 +1,16 @@ +package com.isolaatti.auth.data.local + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserInfoDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun setUserInfo(userInfoEntity: UserInfoEntity) + + @Query("SELECT * FROM user_info WHERE id = :id LIMIT 1") + fun getUserInfo(id: Int): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/local/UserInfoEntity.kt b/app/src/main/java/com/isolaatti/auth/data/local/UserInfoEntity.kt new file mode 100644 index 0000000..530a4a0 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/data/local/UserInfoEntity.kt @@ -0,0 +1,11 @@ +package com.isolaatti.auth.data.local + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "user_info") +data class UserInfoEntity( + @PrimaryKey val id: Int, + val username: String, + val displayName: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt b/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt index 5577a01..9c5c216 100644 --- a/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt +++ b/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt @@ -1,6 +1,7 @@ package com.isolaatti.auth.domain import com.isolaatti.auth.data.remote.AuthTokenDto +import com.isolaatti.auth.data.remote.AuthTokenVerificationDto import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/isolaatti/auth/domain/UserInfo.kt b/app/src/main/java/com/isolaatti/auth/domain/UserInfo.kt new file mode 100644 index 0000000..5772776 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/domain/UserInfo.kt @@ -0,0 +1,15 @@ +package com.isolaatti.auth.domain + +import com.isolaatti.auth.data.local.UserInfoEntity +import com.isolaatti.utils.UrlGen + +data class UserInfo(val id: Int, val username: String, val displayName: String) { + + val imageUrl get() = UrlGen.userProfileImage(id, false) + + companion object { + fun fromEntity(userInfoEntity: UserInfoEntity): UserInfo { + return UserInfo(userInfoEntity.id, userInfoEntity.username, userInfoEntity.displayName) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/domain/UserInfoRepository.kt b/app/src/main/java/com/isolaatti/auth/domain/UserInfoRepository.kt new file mode 100644 index 0000000..62415a7 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/domain/UserInfoRepository.kt @@ -0,0 +1,7 @@ +package com.isolaatti.auth.domain + +import kotlinx.coroutines.flow.Flow + +interface UserInfoRepository { + fun getCurrentUserInfo(): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt b/app/src/main/java/com/isolaatti/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt index 35463a2..b60a61a 100644 --- a/app/src/main/java/com/isolaatti/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt +++ b/app/src/main/java/com/isolaatti/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt @@ -14,6 +14,10 @@ import com.isolaatti.settings.domain.UserIdSetting import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -38,35 +42,35 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett CoroutineScope(Dispatchers.IO).launch { when(options) { POST_OPTIONS -> { - userIdSetting.getUserId()?.let { userId -> + userIdSetting.getUserId().onEach { userId -> _options.postValue( Options.getPostsOptions( - userOwned = userId == payload?.userId, - savable = false, - snapshotAble = false) + userOwned = userId == payload?.userId, + savable = false, + snapshotAble = false) ) _callerId = callerId _payload = payload - } + }.flowOn(Dispatchers.IO).launchIn(this) } COMMENT_OPTIONS -> { - userIdSetting.getUserId()?.let { userId -> + userIdSetting.getUserId().onEach { userId -> _options.postValue( Options.getCommentOptions( - userOwned = userId == payload?.userId, - savable = false, - snapshotAble = false) + userOwned = userId == payload?.userId, + savable = false, + snapshotAble = false) ) _callerId = callerId _payload = payload - } + }.flowOn(Dispatchers.IO).launchIn(this) } PROFILE_PHOTO_OPTIONS -> { - userIdSetting.getUserId()?.let { userId -> + userIdSetting.getUserId().onEach { userId -> _options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,)) _callerId = callerId _payload = payload - } + }.flowOn(Dispatchers.IO).launchIn(this) } } } diff --git a/app/src/main/java/com/isolaatti/database/AppDatabase.kt b/app/src/main/java/com/isolaatti/database/AppDatabase.kt index a40c36b..d46f5fd 100644 --- a/app/src/main/java/com/isolaatti/database/AppDatabase.kt +++ b/app/src/main/java/com/isolaatti/database/AppDatabase.kt @@ -2,11 +2,14 @@ package com.isolaatti.database import androidx.room.Database import androidx.room.RoomDatabase +import com.isolaatti.auth.data.local.UserInfoDao +import com.isolaatti.auth.data.local.UserInfoEntity import com.isolaatti.settings.data.KeyValueDao import com.isolaatti.settings.data.KeyValueEntity -@Database(entities = [KeyValueEntity::class], version = 1) +@Database(entities = [KeyValueEntity::class, UserInfoEntity::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun keyValueDao(): KeyValueDao + abstract fun userInfoDao(): UserInfoDao } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/database/Module.kt b/app/src/main/java/com/isolaatti/database/Module.kt index f16aa64..409f096 100644 --- a/app/src/main/java/com/isolaatti/database/Module.kt +++ b/app/src/main/java/com/isolaatti/database/Module.kt @@ -15,6 +15,8 @@ class Module { @Provides @Singleton fun provideAppDatabase(@ApplicationContext applicationContext: Context): AppDatabase { - return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database.db").build() + return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database.db") + .fallbackToDestructiveMigration() + .build() } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/Module.kt b/app/src/main/java/com/isolaatti/profile/Module.kt index a163ac0..c02ed11 100644 --- a/app/src/main/java/com/isolaatti/profile/Module.kt +++ b/app/src/main/java/com/isolaatti/profile/Module.kt @@ -1,6 +1,8 @@ package com.isolaatti.profile +import com.isolaatti.auth.data.local.UserInfoDao import com.isolaatti.connectivity.RetrofitClient +import com.isolaatti.database.AppDatabase import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.repository.ProfileRepositoryImpl import com.isolaatti.profile.domain.ProfileRepository @@ -18,7 +20,14 @@ class Module { } @Provides - fun provideProfileRepository(profileApi: ProfileApi): ProfileRepository { - return ProfileRepositoryImpl(profileApi) + fun provideUserInfoDao(database: AppDatabase): UserInfoDao { + return database.userInfoDao() } + + @Provides + fun provideProfileRepository(profileApi: ProfileApi, userInfoDao: UserInfoDao): ProfileRepository { + return ProfileRepositoryImpl(profileApi, userInfoDao) + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt index f589671..84d21c9 100644 --- a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt @@ -1,6 +1,8 @@ package com.isolaatti.profile.data.repository import android.util.Log +import com.isolaatti.auth.data.local.UserInfoDao +import com.isolaatti.auth.data.local.UserInfoEntity import com.isolaatti.images.common.domain.entity.Image import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.UpdateProfileDto @@ -12,13 +14,17 @@ import kotlinx.coroutines.flow.flow import retrofit2.awaitResponse import javax.inject.Inject -class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository { +class ProfileRepositoryImpl @Inject constructor( + private val profileApi: ProfileApi, + private val userInfoDao: UserInfoDao) : ProfileRepository { override fun getProfile(userId: Int): Flow> = flow { try { val result = profileApi.userProfile(userId).awaitResponse() if(result.isSuccessful) { val dto = result.body() if(dto != null) { + userInfoDao.setUserInfo(UserInfoEntity(userId, dto.uniqueUsername, dto.name)) + emit(Resource.Success(UserProfile.fromDto(dto))) } } else { diff --git a/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt b/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt index ab59b37..e9eaec3 100644 --- a/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt +++ b/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt @@ -4,12 +4,16 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import kotlinx.coroutines.flow.Flow @Dao interface KeyValueDao { @Query("SELECT value FROM key_values WHERE id = :key") - suspend fun getValue(key: String): String + fun getValue(key: String): Flow @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun setValue(entity: KeyValueEntity) + + @Query("SELECT value FROM key_values WHERE id = :key") + suspend fun getValueAsync(key: String): String? } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt index 722363a..dc449c2 100644 --- a/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt @@ -1,13 +1,18 @@ package com.isolaatti.settings.data import com.isolaatti.settings.domain.SettingsRepository +import kotlinx.coroutines.flow.Flow class SettingsRepositoryImpl(private val keyValueDao: KeyValueDao) : SettingsRepository { override suspend fun setKeyValue(key: String, value: String) { keyValueDao.setValue(KeyValueEntity(key, value)) } - override suspend fun getKeyValue(key: String): String? { + override fun getKeyValue(key: String): Flow { return keyValueDao.getValue(key) } + + override suspend fun getKeyValueAsync(key: String): String? { + return keyValueDao.getValueAsync(key) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt b/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt index 135802e..47320b7 100644 --- a/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt +++ b/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt @@ -1,6 +1,10 @@ package com.isolaatti.settings.domain +import kotlinx.coroutines.flow.Flow + interface SettingsRepository { suspend fun setKeyValue(key: String, value: String) - suspend fun getKeyValue(key: String): String? + fun getKeyValue(key: String): Flow + + suspend fun getKeyValueAsync(key: String): String? } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt b/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt index a87cfe9..061d269 100644 --- a/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt +++ b/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt @@ -2,11 +2,16 @@ package com.isolaatti.settings.domain import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class UserIdSetting @Inject constructor(private val settingsRepository: SettingsRepository) { - suspend fun getUserId(): Int? { - return settingsRepository.getKeyValue(KEY_USER_ID)?.toIntOrNull() + fun getUserId(): Flow { + return settingsRepository.getKeyValue(KEY_USER_ID).map { it?.toIntOrNull() } + } + + suspend fun getUserIdAsync(): Int? { + return settingsRepository.getKeyValueAsync(KEY_USER_ID)?.toIntOrNull() } suspend fun setUserId(userId: Int) { diff --git a/app/src/main/java/com/isolaatti/settings/presentation/SettingsViewModel.kt b/app/src/main/java/com/isolaatti/settings/presentation/SettingsViewModel.kt new file mode 100644 index 0000000..ec11cbf --- /dev/null +++ b/app/src/main/java/com/isolaatti/settings/presentation/SettingsViewModel.kt @@ -0,0 +1,26 @@ +package com.isolaatti.settings.presentation + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.isolaatti.auth.domain.UserInfo +import com.isolaatti.auth.domain.UserInfoRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SettingsViewModel @Inject constructor(private val userInfoRepository: UserInfoRepository) : ViewModel() { + val userInfo: MutableLiveData = MutableLiveData() + fun getUserInfo() { + viewModelScope.launch { + userInfoRepository.getCurrentUserInfo().onEach { + userInfo.postValue(it) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/ui/SettingsFragment.kt b/app/src/main/java/com/isolaatti/settings/ui/SettingsFragment.kt index 540d755..49754be 100644 --- a/app/src/main/java/com/isolaatti/settings/ui/SettingsFragment.kt +++ b/app/src/main/java/com/isolaatti/settings/ui/SettingsFragment.kt @@ -5,11 +5,17 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import coil.load import com.isolaatti.databinding.FragmentSettingsBinding +import com.isolaatti.settings.presentation.SettingsViewModel +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class SettingsFragment : Fragment() { private lateinit var binding: FragmentSettingsBinding + private val viewModel: SettingsViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, @@ -25,6 +31,16 @@ class SettingsFragment : Fragment() { super.onViewCreated(view, savedInstanceState) setupListeners() + setupObservers() + viewModel.getUserInfo() + } + + private fun setupObservers() { + viewModel.userInfo.observe(viewLifecycleOwner) { + binding.textViewUsername.text = "@${it.username}" + binding.textViewDisplayName.text = it.displayName + binding.profileImageView.load(it.imageUrl) + } } private fun setupListeners() {