How to get initial focus in compose - android-jetpack-compose

How to get initial keyboard focus on an Android compose app?
My view looks like
Parent { Child { Button} }
I tried implementing it in the Parent composable function....
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }

The following code woirks like a charm....
fun Modifier.requestInitialFocus() = composed {
val first = remember { FocusRequester() }
LaunchedEffect(first) {
delay(1)
first.requestFocus()
}
focusRequester(first)
}
Original:
This error does not happen when implementing it in the composable function, where the target element is a direct child....
So implementing it in the Child seems to be a solution....

Related

How does this function keep track of clicks?

On the "Thinking in Compose" page I don't get this code, how does $clicks keep track of number of clicks?
#Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")
}
}
I'm learning Kotlin at the same time as Compose, so I get puzzled all the time.
It's omitted in that example but it should store the click-count in a MutableState<T> wrapped with remember
var clickCount by remember { mutableStateOf(0)}
ClickCounter(clicks = clickCount, onClick = {clickCount += it})
For real real real beginner like me, let me add my comment and code.
When the ClickCounter composable is called we need to pass two parameters.
To 'clicks', we pass initial value 0 which will be changed everytime we click the button. Also the initial value need to be 'remembered' and tracked for further changes. So we create new variable using 'mutableStateOf' function inside 'remember' function.
To 'onClick', we need to pass function which adds 1 everytime the button is clicked.
As a caller of the ClickCounter composable, I made a simple preview composable as below.
#Preview(showBackground = true)
#Composable
fun ClickCounterPreview(){
var clicks by remember { mutableStateOf(0) }
// We call ClickCounter composable passing two parameters.
ClickCounter(clicks = clicks, onClick = { clicks += 1 })
}

How do I make rememberSaveable work inside movableContentOf?

I made two similar navigation #Composables with a slot for content, one of them is used depending on the screen size class. I also use rememberSaveable inside the content to handle screen rotation. The problem is, without movableContentOf, their state is handled as separate, but with it, the saved state is gone after every screen rotation. A simplified example:
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val content = remember {
movableContentOf {
var a by remember { mutableStateOf(false) }
Checkbox(a, { a = it })
var b by rememberSaveable { mutableStateOf(false) }
Checkbox(b, { b = it })
}
}
var switch by rememberSaveable { mutableStateOf(false) }
Checkbox(switch, { switch = it })
if (switch) Row { content() } else Column { content() }
}
Mode switching works just fine, but only the upper checkbox keeps its state after screen rotation.
This is intended behaviour: you create movableContentOf inside remember, which means it's gonna be recreated on rotation.
You can store it inside a view model, but if you need to do so, you can store your data there as well, which makes storing movableContentOf redundant: in this case, re-creating it in remember is perfectly fine.

How to open a BasicTextField focused with blinking cursor in it?

I have a BasicTextField in one of my views. I am showing the soft keyboard by default and when I start typing letters on the keyboard, nothing is shown in the BasicTextField, since it has no cursor.
To make my keyboard actions visible, I have to tap into the TextField, to make the cursor visible. Now, whenI tap on the keyboard, I see the result in the BasicTextField.
How can I open the BasicTextField with an active blinking cursor in it?
EDIT: the proposed solution from here did not work for me
val focusRequester = FocusRequester()
val keyboardController = LocalSoftwareKeyboardController.current
//..
.focusRequester(focusRequester)
.onFocusChanged {
if (it.isFocused) {
keyboardController?.show()
}
}
Did neither activated the cursor nor made the keyboard appear. In addition to that
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
leads to a crash:
java.lang.IllegalStateException:
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in
response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
at androidx.compose.ui.focus.FocusRequester.requestFocus(FocusRequester.kt:54)
I got it working:
val focusRequester = FocusRequester()
//..
.focusRequester(focusRequester)
and instead of
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
I used
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}

ViewModel Live Data observers calling on rotation

In my view model, I have two properties:
private val databaseDao = QuestionDatabase.getDatabase(context).questionDao()
val allQuestions: LiveData<List<Question>> = databaseDao.getAllQuestions()
I have observers set on "allQuestions" in my fragment and I'm noticing the observer is being called when I rotate the device. Even though the View Model is only being created once (can tell via a log statement in init()), the observer methods are still being called.
Why is this? I would think the point is to have persistency in the View Model. Ideally, I want the database questions to be only loaded once, regardless of rotation.
This happens because LiveData is lifecycle aware.
And When you rotate the screen you UI Controller [Activity/Fragment] goes through various lifecycle states and lifecycle callbacks.
And since LiveData is lifecycle aware, it updates the detail accordingly.
I have tried to explain this with following points:
When the UI Controller is offscreen, Live Data performs no updates.
When the UI Controller is back on screen, it gets current data.
(Because of this property you are getting above behavior)
When UI controller is destroyed, it performs cleanup on its own.
When new UI Controller starts observing live data, it gets current data.
add this check inside observer
if(lifecycle.currentState == Lifecycle.State.RESUMED){
//code
}
I have the same issue, after reading the jetpack guideline doc, I solve it. Just like what #SVK mentioned, after the rotation of the screen, activity/fragment were re-created.
Base on the solution https://stackoverflow.com/a/64062616,
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun observeForever(observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observeForever { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
#MainThread
override fun setValue(#Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}

Monotouch.Dialog - How to push a view from an Element

It seems like this should be very easy, but I'm missing something. I have a custom Element:
public class PostSummaryElement:StyledMultilineElement,IElementSizing
When the element's accessory is clicked on, I want to push a view onto the stack. I.e. something like this:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (MyParent != null) {
MyParent.PresentViewController(new MyDemoController("Details"),false,null);
}
};
Where MyDemoController's gui is created with monotouch.dialog.
I'm just trying to break up the gui into Views and Controlls, where a control can push a view onto the stack, wiat for something to happen, and then the user navigates back to the previous view wich contains the control.
Any thought?
Thanks.
I'd recommend you not to hardcode behavior in AccessoryTapped method, because the day when you'll want to use that component in another place of your project is very close. And probably in nearest future you'll need some another behavior or for example it will be another project without MyDemoController at all.
So I propose you to create the following property:
public Action accessoryTapped;
in your element and its view, and then modify your AccessoryTapped is that way:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (accessoryTapped != null) {
accessoryTapped();
}
};
So you'll need to create PostSummaryElement objects in following way:
var myElement = new PostSummaryElement() {
accessoryTapped = someFunction,
}
...
void someFunction()
{
NavigationController.PushViewController (new MyDemoController("Details"), true);
}

Resources