I am using jetcompose android with kotlin and i am trying to create a google signup/signin button.
I could only create the SignInButton object. But i didnt find anyway on how to render it.
val signupButton = SignInButton(LocalContext.current)
Package from where i imported SigninButton
com.google.android.gms.common.SignInButton
I am trying to render the signupbutton but unable to render using the above code
The com.google.android.gms.common.SignInButton is a View, not a Composable so it won't be rendered inside of a Composable context. To render Views that are commonly used in XML layouts you should use AndroidView into a Composable context and put the View inside it.
AndroidView(
factory = { ctx ->
SignInButton(ctx).apply {
setOnClickListener {
//Handle on click
Toast.makeText(ctx, "On SignInButton clicked!", Toast.LENGTH_SHORT)
.show()
}
}
},
modifier = Modifier.fillMaxWidth()
)
Related
In a Jetpack Compose application, I have two composables similar to here:
#Composable
fun Main() {
println("Composed Main")
val context = LocalContext.current
var text by remember { mutableStateOf("") }
fun update(num: Number) {
text = num.toString()
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show()
}
Column {
Text(text)
Keypad { update(it) }
}
}
#Composable
fun Keypad(onClick: (Number) -> Unit) {
println("Composed Keypad")
Column {
for (i in 1..10) {
Button(onClick = {onClick(i)}) {
Text(i.toString())
}
}
}
}
Clicking each button causes the two composables to recompose and produces this output:
I/System.out: Composed Main
I/System.out: Composed Keypad
Recomposing the Keypad composable is unneeded and makes the app freeze (for several seconds in a bigger project).
Removing usages of context in the event handles (in here, commenting out the Toast) solves the problem and does not recompose the Keypad and produces this output:
I/System.out: Composed Main
Is there any other way I could use context in an event without causing unneeded recompositions?
The issue is the Context not being a stable (#Stable) type. The lambda/callback of KeyPad is updating a state and its immediately followed by a component that uses an unstable Context, this results to the onClickLambda to be re-created (you can see its hashcode changing everytime you click a button), thus making the Keypad composable not skippable.
You can consider four approaches to deal with your issue. I also made some changes to your code removing the local function and put everything directly in the lambda/callback to make everything smaller.
For the first two, start first by creating a generic wrapper class like this.
#Stable
data class StableWrapper<T>(val value: T)
Wrapping Context in the #Stable wrapper
Using the generic wrapper class, you can consider wrapping the context and use it like this
#Composable
fun Main() {
Log.e("Composable", "Composed Main")
var text by remember { mutableStateOf("") }
val context = LocalContext.current
val contextStableWrapper = StableWrapper(context)
Column {
Text(text)
Keypad {
text = it.toString()
Toast.makeText(contextStableWrapper.value, "Toast", Toast.LENGTH_SHORT).show()
}
}
}
Wrapping your Toast in the #Stable wrapper
Toast is also an unstable type, so you have to make it "stable" with this second approach.
Note that this only applies if your Toast message will not change.
Hoist them up above your Main where you'll create an instance of your static-message Toast and put it inside the stable wrapper
val toastWrapper = StableWrapper(
Toast.makeText(LocalContext.current, "Toast", Toast.LENGTH_SHORT)
)
Main(toastWrapper = toastWrapper)
and your Main composable will look like this
#Composable
fun Main(toastWrapper: StableWrapper<Toast>) {
Log.e("Composable", "Composed Main")
var text by remember { mutableStateOf("") }
Column {
Text(text)
Keypad {
text = it.toString()
toastWrapper.value.show()
}
}
}
remember{…} the Context
(I might expect some correction here), I think this is called "memoizing the value (Context) inside remember{…}", this looks similar to a deferred read.
#Composable
fun Main() {
Log.e("Composable", "Composed Main")
var text by remember { mutableStateOf("") }
val context = LocalContext.current
val rememberedContext = remember { { context } }
Column {
Text(text)
Keypad {
text = it.toString()
Toast.makeText(rememberedContext(), "Toast", Toast.LENGTH_SHORT).show()
}
}
}
Use Side-Effects
You can utilize Compose Side-Effects and put the Toast in them.
Here, SideEffect will execute every post-recomposition.
SideEffect {
if (text.isNotEmpty()) {
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show()
}
}
or you can utilize LaunchedEffect using the text as its key, so on succeeding re-compositions, when the text changes, different from its previous value (invalidates), the LaunchedEffect will re-execute and show the toast again
LaunchedEffect(key1 = text) {
if (text.isNotEmpty()) {
Toast.makeText(context, "Toast", Toast.LENGTH_SHORT).show()
}
}
Replacing your print with Log statements, this is the output of any of the approaches when clicking the buttons
E/Composable: Composed Main // first launch of screen
E/Composable: Composed Keypad // first launch of screen
// succeeding clicks
E/Composable: Composed Main
E/Composable: Composed Main
E/Composable: Composed Main
E/Composable: Composed Main
The only part I'm still not sure of is the first approach, even if Toast is not a stable type based on the second, just wrapping the context in the stable wrapper in the first approach is sufficient enough for the Keypad composable to get skipped.
In Jetpack Compose I'm using AndroidView to display an ad banner from a company called Smart.IO.
At the moment the banner shows when first initialised, but then fails to recompose when user comes back to the screen it's displayed on.
I'm aware of using the update function inside compose view, but I can't find any parameters I could use to essentially update on Banner to trigger the recomposition.
AndroidView(
modifier = Modifier
.fillMaxWidth(),
factory = { context ->
Banner(context as Activity?)
},
update = {
}
)
This could be a library error. You can check if this view behaves normally in normal Android XML.
Or maybe you need to use some API from this library, personally I haven't found any decent documentation or Android SDK source code.
Anyway, here is how you can make your view update.
You can keep track of life-cycle events, as shown in this answer, and only display your view during ON_RESUME. This will take it off the screen when it is paused, and make it create a new view when it resumes:
val lifeCycleState by LocalLifecycleOwner.current.lifecycle.observeAsSate()
if (lifeCycleState == Lifecycle.Event.ON_RESUME) {
AndroidView(
modifier = Modifier
.fillMaxWidth(),
factory = { context ->
Banner(context as Activity?)
},
update = {
}
)
}
Lifecycle.observeAsSate:
#Composable
fun Lifecycle.observeAsSate(): State<Lifecycle.Event> {
val state = remember { mutableStateOf(Lifecycle.Event.ON_ANY) }
DisposableEffect(this) {
val observer = LifecycleEventObserver { _, event ->
state.value = event
}
this#observeAsSate.addObserver(observer)
onDispose {
this#observeAsSate.removeObserver(observer)
}
}
return state
}
I am planning to replace all the fragments in my project with composables. The only fragment remaining is the one with a WebView in it. I need a way to get it's screenshot whenever user clicks report button
Box(Modifier.fillMaxSize()) {
Button(
onClick = this#ViewerFragment::onReportClick,
)
AndroidView(factory = { context ->
MyWebView(context).apply {
loadDataWithBaseURL(htmlString)
addJavascriptInterface(JavaScriptInterface(), "JsIf")
}
}
)
}
Previously; I used to pass the webview from view binding to a utility function for capturing the screenshot.
fun onReportClick() {
val screenshot = ImageUtil(requireContext()).getScreenshot(binding.myWvViewer)
.
.
}
Docs recommend "Constructing the view in the AndroidView viewBlock is the best practice. Do not hold or remember a direct view reference outside AndroidView."
So what could be the best approach?
Check out this answer on how to take a screenshot of any compose view.
In case you need to take screenshot of the full web view(including not visible part), I'm afraid that's an exceptional case when you have to store in remember variable and pass it into your handler:
var webView by remember { mutableStateOf<WebView?>(null) }
AndroidView(
factory = { context ->
WebView(context).apply {
// ...
webView = this
}
},
)
Button(onClick = {
onReportClick(webView!!)
}) {
Text("Capture")
}
In my project I use JetPack Compose and the AndroidView to use an XML View.
#Composable
fun MyComposable(
message: String
) {
AndroidView(
factory = { context ->
TextView(context).apply {
text = message
}
})
}
My issue is that when my message state change, the XML view in the AndroidView isn't recomposed. There is an option in the AndroidView to obverse the state change ?
ps: I've simplified MyComposable for the example
You can use the update block.
From the doc:
The update block can be run multiple times (on the UI thread as well) due to recomposition, and it is the right place to set View properties depending on state. When state changes, the block will be reexecuted to set the new properties. Note the block will also be ran once right after the factory block completes
AndroidView(
factory = { context ->
TextView(context).apply {
text = "Initial Value"
}
},
update = {
it.text = message
}
)
I'm trying to use Robinhood Spark in my app but my entire UI is built using jetpack compose. I'm going through these docs but it's only mentioned how you can use XML resource files like String, Dimensions, Colors, Images, Vector drawbles, Icons, and Fonts. I don't know which one Spark falls under. Looking at the XML code it's a LinearLayout but I can't see how that falls under any of the resource types I've mentioned prior. So for now I'm going through non-compose documentations to see if there is some kind of class or method I can use but would appreciate some feedback and help.
It is as nglauber mentioned, but you would need to define layout parameters that will fit into your compose view as well.
More about it https://foso.github.io/Jetpack-Compose-Playground/viewinterop/androidview/
AndroidView(factory = { ctx ->
val view = LayoutInflater.from(ctx)
.inflate(R.layout.activity_sample, null, false)
.apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
// Use your view as usual...
view
}, update = {
// here update your view or get element from it and update
})
You can use AndroidView to load your XML file.
AndroidView(
factory = { context: Context ->
val view = LayoutInflater.from(context)
.inflate(R.layout.your_layout, null, false)
// Use your view as usual...
view // return the view
},
update = { view ->
// Update view if needed
}
)