galex
07/23/2020, 2:58 PMAdam Powell
07/23/2020, 3:05 PMgalex
07/23/2020, 3:10 PMAdam Powell
07/23/2020, 3:39 PMZach Klippenstein (he/him) [MOD]
07/23/2020, 3:45 PMgalex
07/23/2020, 5:08 PMZac Sweers
07/23/2020, 5:09 PMgalex
07/23/2020, 5:09 PMAdam Powell
07/23/2020, 5:21 PMRicardo C.
07/23/2020, 5:43 PMgalex
07/23/2020, 7:53 PMoverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(FIREBASE_CLIENT_ID)
.requestEmail()
.requestProfile()
.requestId()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
val account = GoogleSignIn.getLastSignedInAccount(this)
Log.d(TAG, "account = $account")
auth = Firebase.auth
val googleSignIn: ActivityResultLauncher<GoogleSignInClient> = registerForActivityResult(GoogleSignInContract()) { googleSignInAccount ->
googleSignInAccount?.let { firebaseAuthWithGoogle(it) }
}
setContent {
Providers(GoogleSignInAmbient provides googleSignIn, GoogleSignInClientAmbient provides googleSignInClient) {
MainScreen(navigationViewModel)
}
}
}
Here is the Composable using those ambients:
@Composable
fun ProfileScreen() {
val googleSignInResultLauncher = GoogleSignInAmbient.current
val googleSignInClient = GoogleSignInClientAmbient.current
Column {
val auth = Firebase.auth
Text("See all your stuff here, dear ${auth.currentUser?.displayName ?: "John Doe"}")
TextButton(onClick = { googleSignInResultLauncher.launch(googleSignInClient) }) {
Text("Sign In with Google!")
}
}
}
Ricardo C.
07/23/2020, 8:03 PMinterface Controller {
fun onActive() {}
fun onDispose() {}
}
@Composable
fun <C : Controller> Screen(
controller: C,
children: @Composable (C) -> Unit
) {
onActive {
controller.onActive()
}
onDispose {
controller.onDispose()
}
children(controller)
}
class BrowseController @Inject constructor(
private val getSubredditPosts: GetSubredditPosts
) : Controller {
private val _subreddit = MutableStateFlow("androiddev")
val subreddit: StateFlow<String>
get() = _subreddit
val posts: Flow<List<Post>>
get() = subreddit.map {
val params = GetSubredditPosts.Params(subreddit = it)
getSubredditPosts(params).data
}
}
@Composable
fun BrowseScreen(controller: BrowseController) {
Screen(controller) {
val subreddit = controller.subreddit.collectAsState()
val posts = controller.posts.collectAsState(emptyList())
Column {
Text(
text = "Posts for ${subreddit.value}",
modifier = Modifier.fillMaxWidth()
.drawBackground(purple200)
.gravity(Alignment.CenterHorizontally)
.padding(8.dp)
)
ScrollableColumn {
posts.value.forEach { post ->
Post(post)
}
}
}
}
}
I'm trying something alone those lines. I inject the controller and pass it to the screen composable. I'm not expecting the smaller composables to have access to more than data. Only the root one for the screenZach Klippenstein (he/him) [MOD]
07/23/2020, 8:18 PMfun <C : Controller> Screen(
controller: C,
children: @Composable (C) -> Unit
) {
Why does children
take C
as an argument? The caller, who’s already passing the controller to this function, can just reference the same object in the lambda it also passes.Ricardo C.
07/23/2020, 8:19 PMZach Klippenstein (he/him) [MOD]
07/23/2020, 8:19 PMonActive {
controller.onActive()
}
onDispose {
controller.onDispose()
}
If your Controller
class implements CompositionLifecycleObserver
, then these lifecycle methods will automatically be called from wherever the Controller
is `remember`ed, if you’re using remember
to store it.
E.g.
class Controller : CompositionLifecycleObserver {
override fun onEnter { … }
override fun onLeave { … }
}
@Composable fun Main() {
val controller = remember { Controller() }
…
}
But if the point of Controller
is to be provided by an Activity
, then this wouldn’t be useful.Ricardo C.
07/23/2020, 8:24 PMgalex
07/24/2020, 2:14 PMAdam Powell
07/24/2020, 2:24 PMgalex
07/24/2020, 3:04 PMAdam Powell
07/24/2020, 3:45 PMsuspendCancellableCoroutine
, you can use the scope returned by rememberCoroutineScope()
to launch a call to it in a user action event handlerrememberCoroutineScope
call leaves the compositionlaunchInComposition
might be a more appropriate place to do thingsasync
the login attempt on a different, wider scope and await
the result in the composition scope somewheregalex
07/24/2020, 4:17 PMpackage il.co.galex.alexpizzapp.utils
import android.content.Context
import android.content.ContextWrapper
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.*
import androidx.ui.core.ContextAmbient
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import il.co.galex.alexpizzapp.feature.user.UserRepository
import il.co.galex.alexpizzapp.model.User
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@OptIn(ExperimentalComposeApi::class)
@Composable
fun <I, O> ActivityResultRegistry.activityResultLauncher(
requestContract: ActivityResultContract<I, O>,
onResult: (O) -> Unit
): ActivityResultLauncher<I> {
val key = currentComposer.currentCompoundKeyHash.toString()
val launcher = remember(requestContract, onResult) {
register(key, requestContract, onResult)
}
onDispose {
launcher.unregister()
}
return launcher
}
fun Context.findActivity(): AppCompatActivity? {
var context = this
while (context is ContextWrapper) {
if (context is AppCompatActivity) return context
context = context.baseContext
}
return null
}
class GoogleSignInState(
private val googleSignInClient: GoogleSignInClient,
val signInState: State<User?>,
private val launcher: ActivityResultLauncher<GoogleSignInClient>
) {
fun launchSignInRequest() = launcher.launch(googleSignInClient)
}
@Composable
fun ActivityResultRegistry.googleSignIn(googleSignInClient: GoogleSignInClient): GoogleSignInState {
val context = ContextAmbient.current
//val activity = context.findActivity()
val signInState = mutableStateOf<User?>(null)
val launcher = activityResultLauncher(GoogleSignInContract()) {
GlobalScope.launch {
signInState.value = firebaseAuthWithGoogle(it!!)
}
}
return remember(launcher) {
GoogleSignInState(googleSignInClient, signInState, launcher)
}
}
suspend fun firebaseAuthWithGoogle(account: GoogleSignInAccount): User? {
val credential = GoogleAuthProvider.getCredential(account.idToken, null)
return suspendCoroutine { continuation ->
Firebase.auth.signInWithCredential(credential).addOnCompleteListener { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
val firebaseUser = Firebase.auth.currentUser
val repo = UserRepository()
account.toUser(firebaseUser!!.uid).let { user ->
repo.add(user) {
// TODO something nice to show to the user when he is officially in the Firebase User table
continuation.resume(user)
}
}
} else {
// If sign in fails, display a message to the user.
continuation.resume(null)
}
}
}
}
Ian Lake
08/09/2020, 12:59 AMaddOnCompletionListener
necessitybrandonmcansh
08/09/2020, 1:00 AM