Restore keyboard after rotation in Jetpack Compose - material-design

I have a TextField on one of my app screens. When I rotate my device the text field retains the value, but not the soft keyboard focus.
How could I keep the focus and prevent the keyboard from disappearing?
Here is a simplified version of the composable for the screen:
#Composable
fun LoginScreen(
uiState: LoginUiState,
) {
MyTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollableState)
.imePadding(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = uiState.email,
enabled = !uiState.isLoggingIn
)
}
}
}
}
The UI state comes from the model.

You are using the a state-preserver like a ViewModel here, I suppose. You could either store the value in a rememberSaveable block, as Nikola suggests, or you could simply put a simple Boolean where you put the uiState parameter. There's no need to use MutableState<T> this way. Also, no side-effects are required. Just create a parameter.
#Composable
fun MyFiled(
loginState: ... ,
isFocused: Boolean
){
if (isFocused)
focusRequestor.requestFocus()
...
}
Just put a simple condition, and it'll do.

You need to use rememberSaveable to store wither the TextField was focused previously.
val focusRequester = remember { FocusRequester() }
var hasFocus by rememberSaveable { mutableStateOf(false) }
TextField(
value = ...,
onValueChange = { ... },
modifier = Modifier.focusRequester(focusRequester).onFocusChanged {
hasFocus = it.hasFocus
}
)
LaunchedEffect(hasFocus){
if(hasFocus) {
focusRequester.requestFocus()
}
}

Related

Get value from RoomDB into Composable function from Jetpack Compose

I have looked for many solutions but I found it as a newbie very
complex on how to solve it properly without throwing away all my backend
code.
I want to get an Float value from my RoomDB into a composable
UI value but as far as we all know getting RoomDB values with queries
needs an asynchronus scope. And those aren't capable of returning values
because values stay within a scope and die there too. There is I think
no way to no use Coroutine Scopes or anything else that doesn't block the UI loading
so it can actually work.
What can I do? I don't want to throw away the entire RoomDB database
neither our Jetpack Compose base GUI?
I tried replacing the 0.8f with a method that calls a Coroutine Scope which
should idealistically return a Float value to this part of our code.
#Composable
fun ChargeScreen(){
val context = LocalContext.current
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
){
Column {
ChargeTopText()
CircularChargeBar(percentage = 0.8f, number =100 )
}
}
}
I am also new at Android Jetpack Compose but I can give you a suggestion for your case.
Take a look at the code below
#Composable
fun YourComposable() {
//Use remember for state management
//Read more at https://developer.android.com/jetpack/compose/state
var floatData by remember { mutableStateOf(0F) }
var isLoading by remember { mutableStateOf(true) }
//Side-effects
//Read more at https://developer.android.com/jetpack/compose/side-effects
LaunchedEffect(Unit) {
isLoading = true
floatData = getFloatDataFromDB()
isLoading = false
}
//Just a way to show a progress indicator while we are getting the value from DB.
if(!isLoading) {
Text(text = "FloatData: $floatData")
} else {
CircularProgressIndicator(
modifier = Modifier.size(50.dp),
color = Color.Green,
strokeWidth = 5.dp)
}
}
suspend fun getFloatDataFromDB(): Float {
//Using withContext(Dispatchers.IO) so it will execute in IO Thread.
return withContext(Dispatchers.IO) {
//Pretend this will take 5 seconds to complete
Thread.sleep(5000)
//And return the value
return#withContext 0.9F
}
}
I hope this will help you out!

Jetpack Compose Button goes above keyboard?

I have some content and a TextField, and a button on the bottom of the screen.
When I tap on the TextField, the keyboard covers my button, but obviously I want the button to go above the keyboard.
How can I do that?
I already added this line to my MainActivity, before setContent():
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
My Scaffold:
Scaffold { padding ->
Box(
modifier = Modifier
.padding(20.dp)
.padding(top = 40.dp)
.fillMaxSize()
) {
Column {
Text("My content")
Text("My content")
Text("My content")
TextField(
value = text,
onValueChange = { text = it },
)
}
Box(
modifier = Modifier.align(Alignment.BottomCenter)
) {
Button(content = { Text("BTN") }, onClick = {})
}
}
}
Add android:windowSoftInputMode="adjustResize" to the AndroidManifest.xml. You're cured!

How to create a ColorDrawable from Jetpack Compose Color?

Is there a way to convert jetpack Compose's androidx.compose.ui.graphics.Color to android.graphics.drawable.ColorDrawable ?
I tried ColorDrawable(Color.Red.toArgb()) but its not working!
You tried it in the correct way.
Here is a sample that does work
#Composable
fun MyComposable() {
val color = androidx.compose.ui.graphics.Color.Red
AndroidView(
modifier = Modifier
.fillMaxWidth()
.height(20.dp),
factory = { context ->
View(context).apply {
background = ColorDrawable(color.toArgb())
}
}
)
}

Android Jetpack Passing Data Between Composables

I'm trying to pass a constantly updating variable "message" across my Jetpack Composables. I have a draggable box that tracks the coordinates of the box but I'm trying to send the real-time data through a TCP connection. However, I noticed that the current coordinate of the draggable box isn't passing through to the other Composable or the socket -only the same value is passed despite message changing continuously due to me dragging the box. Also, the moment dataSendButton() is pressed, the createDragImage() and its draggable box stops animating/running.
var message = "" // global Android send message
class MainActivity : ComponentActivity() {
private var textView: TextView? = null
dataSendButton()
createDragImage()
...
}
}
}
#Composable
fun createDragImage(){
val context = LocalContext.current
...
Box() {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Transparent)
.size(150.dp)
.border(BorderStroke(4.dp, SolidColor(Color.Red)))
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX = someConstantX
offsetY += dragAmount.y
message = offsetY.toString()
...
#Composable
fun dataSendButton() {
val context = LocalContext.current
...
Button(
onClick = {
// **ISSUE: message in this composable is not getting updated with message value from createDragImage()
val b1 = MainActivity.TCPconnector_client(context, message)
b1.execute()
},
{
Text(text = "Send Data", color = Color.White, fontSize = 20.sp)
}
}
}
}
}
It is because that is not how you store state in Compose.
Change the declaration of the variable.
var message by mutableStateOf(...)
Then the changes to it will trigger a recomposition, and so the rest of the code should remain the same. It is always recommended to store the state holders in a viewmodel, and pass the viewmodel around instead.
This is a working code with viewmodel
class MainActivity : ComponentActivity() {
private var textView: TextView? = null
val vm by viewmodels<MViewModel>()
dataSendButton(vm.message, vm:: onMessageChange)
createDragImage(vm.message)
...
}
}
}
#Composable
fun createDragImage(message: String, onMessageChange: (String) -> Unit){
val context = LocalContext.current
...
Box() {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Transparent)
.size(150.dp)
.border(BorderStroke(4.dp, SolidColor(Color.Red)))
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX = someConstantX
offsetY += dragAmount.y
onMessageChange (offsetY.toString())
...
#Composable
fun dataSendButton(message: String) {
val context = LocalContext.current
...
Button(
onClick = {
// **ISSUE: message in this composable is not getting updated with message value from createDragImage() // This seems to be an error. Calling a Composable from onClick?
val b1 = MainActivity.TCPconnector_client(context, message)
b1.execute()
},
{
Text(text = "Send Data", color = Color.White, fontSize = 20.sp)
}
}
}
}
}
class MViewModel: ViewModel(){
var message by mutableStateOf("")
private set //do not allow external modifications to ensure consistency
fun onMessageChange (newMessage: String){
message = newMessage
}
}
Note this is the ideal way of doing such implementation. However, for your specific case, if you do not need to access it anywhere else, only changing the declaration as described in the second line of the answer should do
Thanks

Action when user click on the delete button on the keyboard in SwiftUI

I try to run a function when the user click on the delete button on the keyboard when he try to modify a Textfield.
How can I do that ?
Yes it is possible, however it requires subclassing UITextField and creating your own UIViewRepresentable
This answer is based on the fantastic work done by Costantino Pistagna in his medium article but we need to do a little more work.
Firstly we need to create our subclass of UITextField, this should also conform to the UITextFieldDelegate protocol.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
var deleteHandler: (() -> Void)?
override func deleteBackward() {
super.deleteBackward()
deleteHandler?()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
Because we are creating our own implementation of a TextField we need three functions that we can use for callbacks.
textFieldChangeHandler this will be called when the text property updates and allows us to change the state value associated with our Textfield.
onCommitHandler this will be called when we have finished editing our TextField
deleteHandler this will be called when we perform he delete action.
The code above shows how these are used. The part that you are particularly interested in is the override func deleteBackward(), by overriding this we are able to hook into when the delete button is pressed and perform an action on it. Depending on your use case, you may want the deleteHandler to be called before you call the super.
Next we need to create our UIViewRepresentable.
struct MyTextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
var deleteHandler: (()->Void)?
func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
tmpView.deleteHandler = deleteHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<MyTextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
This is where we create our SwiftUI version of our WrappableTextField. We create our WrappableTextField and its properties. In the makeUIView function we assign these properties. Finally in the updateUIView we set the content hugging properties, but you may choose not to do that, it really depends on your use case.
Finally we can create a small working example.
struct ContentView: View {
#State var text = ""
var body: some View {
MyTextField(tag: 0, placeholder: "Enter your name here", changeHandler: { text in
// update the state's value of text
self.text = text
}, onCommitHandler: {
// do something when the editing finishes
print("Editing ended")
}, deleteHandler: {
// do something here when you press delete
print("Delete pressed")
})
}
}

Resources