https://kotlinlang.org logo
j

Jasmin Fajkic

09/01/2022, 5:48 PM
I have hard time ( as a new guy to android and JC) to understand why I can not get update from stateflow into my composable. Code in thread
This is my session manager interface
Copy code
interface SessionManager {
    val currentProfileComplete: Boolean
    val hasSavedProfile: Boolean
    val session: Session

    fun killSession()
    fun saveProfile(profile: ProfileResponse?)
    suspend fun getCurrentUserId(): Resource<Int>?
}

data class Session(
    var profile: ProfileResponse?,
    var isAuthenticated: Boolean,
)
This implementation of SessionManager
Copy code
class SessionManagerImpl @Inject constructor(
    private val credentialsManager: CredentialsManager,
) : SessionManager {

    private var _session = MutableStateFlow(Session(null, false))
    override val session = _session.asStateFlow().value

    override fun killSession() {
        credentialsManager.removeCredentials()
        _session.update { it.copy(profile = null, isAuthenticated = false) }
    }

    override fun saveProfile(profile: ProfileResponse?) {
        Log.d("Prof", profile.toString())
        _session.value = Session(profile, isAuthenticated = true)
    }

    override suspend fun getCurrentUserId(): Resource<Int>? {
        return when (val userId = credentialsManager.getUserIdFromToken()) {
            is Resource.Success -> userId
            is Resource.Error -> null
            else -> null
        }
    }

    override val hasSavedProfile = session.profile != null

    override val currentProfileComplete =
        hasSavedProfile && (session.profile?.status != null || session.profile?.status === "completed")

}
And in MainActivity i get SessionManager and pass it down to child but it actually never change data
Copy code
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    @Inject
    lateinit var sessionManager: SessionManager

    override fun onCreate(savedInstanceState: Bundle?) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        super.onCreate(savedInstanceState)
        val intent = Intent()

        FirebaseApp.initializeApp(this)
        Firebase.dynamicLinks
            .getDynamicLink(intent)
            .addOnSuccessListener(this) { pendingDynamicLinkData: PendingDynamicLinkData? ->
                if (pendingDynamicLinkData != null) {
                    intent.data = pendingDynamicLinkData.link
                    this.startActivity(intent)
                }
            }
            .addOnFailureListener(this) { e -> Log.w(TAG, "getDynamicLink:onFailure", e) }

        setContent {
            PhoenixTheme {
                BottomTabView(sessionManager)
            }
        }
    }
}
what I am doing wrong 😞
s

Stylianos Gakis

09/01/2022, 5:53 PM
override val session = _session.asStateFlow().value
I think it's there. You're assigning the initial value of
_session
into
session
and that's it, you never change that
j

Jasmin Fajkic

09/01/2022, 5:54 PM
Hmm probably. Any idea how to solve this properly?
s

Stylianos Gakis

09/01/2022, 5:55 PM
Yeah don't call .value on it and expose it as a StateFlow instead and convert it into state in your composable instead with .collectAsState(WithLifecycle)
Try that for now and see if it fixes the problem and.you can go from there.
j

Jasmin Fajkic

09/01/2022, 5:56 PM
So initially to do this ?
Copy code
interface SessionManager {
    val currentProfileComplete: Boolean
    val hasSavedProfile: Boolean
    val session: StateFlow<Session>

    fun killSession()
    fun saveProfile(profile: ProfileResponse?)
    suspend fun getCurrentUserId(): Resource<Int>?
}

data class Session(
    var profile: ProfileResponse?,
    var isAuthenticated: Boolean,
)
Same 😞 . I get initial value always.
s

Stylianos Gakis

09/01/2022, 6:01 PM
How are you collecting the value?
j

Jasmin Fajkic

09/01/2022, 6:01 PM
Copy code
val session = sessionManager.session.collectAsState()
s

Stylianos Gakis

09/01/2022, 6:04 PM
Try debugging by hardcoding your session to be smth like
session =
flow { while(true) { emit(smth); delay(500); emit(smthElse); delay(500); } }.stateIn(...)
And see if this updates your UI.
j

Jasmin Fajkic

09/01/2022, 6:27 PM
this is thing i do not get it. My loginviewmodel get in constructor sessionmanager and when i access session manager through viewmodel it works. when i access it through composable params it does not work.
Copy code
@Composable
fun Login(navigateToMain: () ->Unit, sessionManager: SessionManager) {
    val loginViewModel: LoginViewModel = hiltViewModel()
    val loginState = loginViewModel.loginState.collectAsState().value

    val session = sessionManager.session.collectAsState()
    val session1 = loginViewModel.sessionManager.session.collectAsState()
    val coroutineScope = rememberCoroutineScope()

    LaunchedEffect(session.value.isAuthenticated) {
        if(session.value.isAuthenticated) {
            navigateToMain()
        }
    }
    Log.d("ISAUTH1", session.value.toString())
    Log.d("ISAUTH2", session1.value.toString())
so second one works fine first does not react on update 😞
I am thinking that in this case even my session manager is Singleton i get two different SessionManagers in LoginViewModel and MainActivity and because from LoginViewModel I am doing some updates of session it works there and not from MainActivity. @Stylianos Gakis what you think?
Here is how I set session manager with DI
Copy code
@Module
@InstallIn(SingletonComponent::class)
object SessionManagerModule {

    @Provides
    fun providesSessionManager(
        credentialsManager: CredentialsManager,
    ): SessionManager =
        SessionManagerImpl(credentialsManager)

}
Just put this and all works fine
Copy code
@Provides
@Singleton
fun providesSessionManager(
    credentialsManager: CredentialsManager,
): SessionManager =
    SessionManagerImpl(credentialsManager)
s

Stylianos Gakis

09/01/2022, 8:41 PM
Yeap without the singleton annotation you would indeed not get the same instance. Glad you figured it out after all ☺️
12 Views