Peter Mandeljc
07/18/2021, 4:56 PM@HiltViewModel
class WorldViewModel @Inject constructor(): ViewModel() {
fun hello() {
}
}
@Composable
fun World(vm: WorldViewModel = hiltViewModel()) {
Continent(onClick = vm::hello)
}
@Composable
fun Continent(onClick: () -> Unit) {
Country(onClick)
}
@Composable
fun Country(onClick: () -> Unit) {
State(onClick)
}
@Composable
fun State(onClick: () -> Unit) {
Button(onClick = onClick) { }
}
Peter Mandeljc
07/18/2021, 4:57 PMval LocalActiveVM = compositionLocalOf<WorldViewModel> { error("facepalm") }
@Composable
fun World(vm: WorldViewModel = hiltViewModel()) {
CompositionLocalProvider(LocalActiveVM provides vm) {
Continent()
}
}
@Composable
fun Continent() {
Country()
}
@Composable
fun Country() {
State()
}
@Composable
fun State() {
val vm = LocalActiveVM.current
Button(onClick = vm::hello) { }
}
CLOVIS
07/18/2021, 5:05 PMAdam Powell
07/18/2021, 5:14 PMAdam Powell
07/18/2021, 5:17 PMJason Inbody
07/18/2021, 5:20 PMPeter Mandeljc
07/18/2021, 5:24 PMAdam Powell
07/18/2021, 5:30 PMAdam Powell
07/18/2021, 5:33 PMCountry
is traversing a data structure of states and calling State
for each one, it's probably going to add some additional data to the callback you would pass to Country
, doing a bit of function currying for the onClick
it passes down to each call to State
and perhaps reporting back up something like an onStateClick: (State) -> Unit
to the caller of Country
Adam Powell
07/18/2021, 5:33 PMAdam Powell
07/18/2021, 5:34 PMAdam Powell
07/18/2021, 5:39 PMPeter Mandeljc
07/18/2021, 6:13 PMAdam Powell
07/18/2021, 6:20 PMclass MyClass {
private var localData: Int = 0
fun doSomething(input: Int) {
localData = input
performDoSomething()
}
private fun performDoSomething() {
someDependency.doWork(localData)
}
}
?Adam Powell
07/18/2021, 6:21 PMPeter Mandeljc
07/18/2021, 6:22 PMJason Inbody
07/18/2021, 6:47 PM@Composable
into callbacks. Maybe I can send an entire user data object + a callback through all the callbacks to update them once the user signs in? I can't fathom passing all these single states + their callbacks and update them all once my user is finally logged in.Jason Inbody
07/18/2021, 6:55 PMAdam Powell
07/18/2021, 7:00 PMAdam Powell
07/18/2021, 7:01 PMJason Inbody
07/18/2021, 7:05 PMstruct AccountView : View {
@ObservedObject private var finUser = FinUser.sharedInstance
@State var paymentView: Int? = nil
@State var signoutView: Int? = nil
@State var passwordResetView: Int? = nil
@State var showDropIn = false
@State var showingAccountSheet: Bool = false
@State var showingPasswordSheet: Bool = false
var body: some View {
VStack {
HStack{
Image(systemName: "person.crop.circle.fill")
.padding()
.foregroundColor(Color.gray)
.font(.system(size: 60))
Spacer()
}
if(!showDropIn){
DynamicContentCell(imgName: "none", titleStr: "First Name", contentStr: $finUser.firstName)
DynamicContentCell(imgName: "none", titleStr: "Last Name", contentStr: $finUser.lastName)
DynamicContentCell(imgName: "greaterthan", titleStr: "Email", contentStr: $finUser.email).onTapGesture {
showingAccountSheet = true
}
NavigationLink(destination: ResetPasswordView(), tag: 1, selection: $passwordResetView) {
ContentCell(imgName: "greaterthan", titleStr: "Password", contentStr: "****************").onTapGesture {
self.passwordResetView = 1
}
}
PaymentCell(paymentType: $finUser.paymentType, titleStr: "Payment", contentStr: $finUser.paymentIdentifier).onTapGesture {
if(!showDropIn){
showDropIn = true
}
}
}
Divider()
Spacer()
if self.showDropIn {
let token = finUser.userData["customerToken"].stringValue
BTDropInRepresentable(authorization: token, handler: { controller, result, error in
if let error = error {
print("Error: \(error.localizedDescription)")
} else if let result = result, result.isCancelled {
handleCancelMethod()
} else {
handlePaymentMethod(result: result)
}
self.showDropIn = false
}).edgesIgnoringSafeArea(.vertical)
}
}.navigationBarTitle(Text("Account"))
.background(EmptyView().actionSheet(isPresented: $showingAccountSheet) {
ActionSheet(title: Text("Sign Out?"), message: Text("Sign out or delete your account."), buttons: [
.default(Text("Sign Out")) { _ = finUser.signOutUser() },
.default(Text("Delete Account")) { finUser.removeUser() },
.cancel()
])})
}
So in my swift code I can update my FinUser singleton+observable object after signing in my user. The data from my server populates the object properties and triggers a state update to add things like $finUser.firstName
Jason Inbody
07/18/2021, 7:06 PMval firstName = rememberSaveable { mutableStateOf("") }
be passed into my signin flow + the callback. This firstName
val would also need to passed into the account viewAdam Powell
07/18/2021, 7:14 PMFinUser.sharedInstance
generally maps to something like a global or CompositionLocal
and would work the same way here should you choose to set things up that way. If you pass a FinUser
instance as a parameter to a @Composable
function it'll both recompose if the instance changes at the call site, and granularly recompose if any observable mutable properties of it changeJason Inbody
07/18/2021, 7:16 PMJason Inbody
07/18/2021, 7:16 PMJason Inbody
07/18/2021, 7:17 PM@Composable
functions wouldn't work as callbacks for some reasonJason Inbody
07/18/2021, 7:18 PMorg.jetbrains.kotlin.diagnostics.SimpleDiagnostic@5219232e (error: could not render message)
in android studio when I try to pass it a @Composable
functionAdam Powell
07/18/2021, 7:20 PMAdam Powell
07/18/2021, 7:22 PMCompositionLocals
have to be read in composition but you can read them into a local val and use that val in callbacks, e.g.
val current = LocalFoo.current
Button(onClick = { current.doStuff() }) { ...
Jason Inbody
07/18/2021, 7:22 PM@Composable
fun loginUser(email: String, password: String){
var finUser = FinUser()
if(isValidEmail(email) && password.length >= 8) {
val hashedPassword = createHash(unhashed = password)
finUser.loginUser(email = email, password = hashedPassword,
onCompletion = {rspObj -> loginUserRsp(response = rspObj)},
onError = {rspObj -> loginUserError(response = rspObj) })
} else if(!isValidEmail(email)){
// handle email mistake
}else if(password.length < 8){
// handle password not long enough
}
}
Jason Inbody
07/18/2021, 7:22 PMButton(
onClick = {loginUser(email = email.toString(),
password = password.toString())
},
modifier = modifier.then(Modifier.shadow(elevation = 5.dp))
) {
Text("Login")
}
Jason Inbody
07/18/2021, 7:22 PMJason Inbody
07/18/2021, 7:23 PM@Composable
fun loginUserRsp(response: MutableMap<String, Any>){
if(response.containsKey("key")){
val finUser = LocalActiveUser.current
finUser.setToken(token = response["key"] as String, tokenType = "Token")
finUser.loggedIn = true
}else{
//TODO ADD ERROR
}
}
Adam Powell
07/18/2021, 7:23 PMloginUser
in that example should not be @Composable
Adam Powell
07/18/2021, 7:23 PMloginUserResp
Jason Inbody
07/18/2021, 7:24 PMval finUser = LocalActiveUser.current
Jason Inbody
07/18/2021, 7:24 PMJason Inbody
07/18/2021, 7:27 PMfun App() {
val finUser = FinUser()
CompositionLocalProvider(LocalActiveUser provides finUser) { *** }
Jason Inbody
07/18/2021, 7:28 PMval LocalActiveUser = compositionLocalOf<FinUser> { error("No user found!") }
class FinUser { *** }
Jason Inbody
07/18/2021, 7:29 PM/Users/jasoninbody/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/view/LoginView.kt: (124, 5): Functions which invoke @Composable functions must be marked with the @Composable annotation
Adam Powell
07/18/2021, 7:29 PMval finUser = remember { FinUser() }
to make sure you don't create new ones when App
recomposes)Adam Powell
07/18/2021, 7:29 PMJason Inbody
07/18/2021, 7:30 PMJason Inbody
07/18/2021, 7:49 PM@Composable
to get the composition local. I'll have to chain a map and callback through all the networking I guessAdam Powell
07/18/2021, 7:58 PMJason Inbody
07/18/2021, 8:05 PMself.email = ""
ย ย ย ย self.firstName =ย ""
ย ย ย ย self.lastName = ""
ย ย ย ย self.accountPassword = "****************"
ย ย ย ย self.token = ""
ย ย ย ย self.tokenType = ""
ย ย ย ย self.userData = [:]
ย ย ย ย self.paymentType = ""
ย ย ย ย self.paymentIdentifier = "No Payment On File"
ย ย ย ย self.paymentToken = ""
ย ย ย ย self.activeRentals = []
ย ย ย ย self.historyRentals = []
I was thinking I could make a data class like that would have key/value pairs "email":"<mailto:email@email.com|email@email.com>"
in kotlin
data class UserData(
val email: String?,
val firstName: String?,
val lastName: String?,
// ect
)
Jason Inbody
07/18/2021, 8:06 PMJason Inbody
07/18/2021, 8:07 PMAdam Powell
07/18/2021, 8:09 PMAdam Powell
07/18/2021, 8:11 PMvar something by mutableStateOf(initial)
in classes, even at the top-level as globals if you want, and it's observable. Compose will automatically observe changes to any snapshot state container declared this wayJason Inbody
07/18/2021, 8:12 PMAdam Powell
07/18/2021, 8:13 PMby mutableStateOf(initialValue)
:
class MyClass(
initialFoo: Foo,
initialBar: Bar
) {
var foo by mutableStateOf(initialFoo)
var bar by mutableStateOf(initialBar)
private set // this can only be changed by this class
// ...
}
Adam Powell
07/18/2021, 8:15 PM@Composable
functions you use var foo by remember { mutableStateOf(initial) }
because you want the same observable holder instance to persist across recompositions - be part of that instance of the composable, not recreate every time something about it recomposesAdam Powell
07/18/2021, 8:16 PMMyClass
from above and write
val myClassInstance = remember { MyClass() }
and use it, since myClassInstance.foo
and myClassInstance.bar
are observableJason Inbody
07/18/2021, 8:22 PMAdam Powell
07/18/2021, 8:24 PM