Get value from RoomDB into Composable function from Jetpack Compose - get

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!

Related

Restore keyboard after rotation in Jetpack Compose

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()
}
}

Getting weird Error when trying LazyColumn() in Jetpack compose

I am trying to run a simple LazyColumn Object, but am unable to run it without this weird error.
Here is my code:
#Composable
fun Test(){
LazyColumn() {
Text(text = "Placeholder", fontSize= 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
Here are the errors:
org.jetbrains.kotlin.diagnostics.SimpleDiagnostic#74c0fa2 (error: could not render message)
org.jetbrains.kotlin.diagnostics.SimpleDiagnostic#c077eec3 (error: could not render message)
Is it something wrong with my code, or is it a bug?
*I wanted to test the scroll function by copy and pasting the lines after the LazyColumn() statement over and over
With 1.0.0-beta04 you can use:
val itemsList = (0..50).toList()
LazyColumn() {
items(itemsList) {
Text(text = "Placeholder", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
In the LazyListScope in order to display the items you have to use one the provided functions:item, items, itemsindexed and stickyHeader.
The error studio should be showing is #Composable invocations can only happen from the context of a #Composable function; this is the error you get when you would compile this function. That Studio shows (error: could not render message) is a known bug that the team is working on.
The reason the compose compiler plugin generates this error is the lambda expected by LazyColumn is not a composable lambda but is the LazyList DSL in which the column is described. For example, something like,
#Composable
fun Test(){
LazyColumn() {
items(10_000) {
Text(text = "Placeholder $it", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
}
is probably what you wanted. It doesn't create 10,000 items, it only creates enough to fit on the screen and will create additional rows as needed (discarding rows as they are occluded) up to row 9,999.
Try this:
#Composable
fun Test(){
LazyColumn() {
for (i in 1..10) {
TestItem(i)
}
}
}
#Composable
fun TestItem(i: Int) {
Text(text = "Placeholder $i", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}

AndroidStudio kotlin AlertDialogue with spinner populated with setItems

I want to show a modal dialogue for the user to select a Bluetooth device in the case that I can't guess it from the device names.
It appears that AlertDialogue has the facility to show a spinner / dropdown.
The alert dialogue builder has a method setItems which I seem to think is what I need, however, its parameter is CharSequence[] but I have some sort of array of strings (I can't tell exactly what I have because everything is just val).
private fun showDialog() {
val names = (bta!!.bondedDevices).map { z -> z.name };
// What is the type of names? How can you find this out?
// How can you make it into a CharSequence[]?
val ab = AlertDialog.Builder(this);
ab.setTitle("Select device");
ab.setIcon(android.R.drawable.ic_dialog_alert); // I'd prefer a question mark.
ab.setPositiveButton("Select"){dialogueInterface, which -> Toast.makeText(applicationContext, "Selected", Toast.LENGTH_LONG).show()};
ab.setNeutralButton("Cancel"){dialogueInterface, which -> Toast.makeText(applicationContext, "Cancelled", Toast.LENGTH_LONG).show()};
ab.setItems(names); // None of the following functions can be called with the arguments supplied.
val a = ab.create();
a.setCancelable(false);
a.show();
}
I think this works in Java, but it doesn't in kotlin
CharSequence[] cs = list.toArray(new CharSequence[list.size()]);
So:
In AndroidStudio how can you tell the type of a variable? (In VisualStudio if you hover over a var then the tooltip tells you.)
How in kotlin do you make a CharSequence[]?
fun elmFind() {
// Find device
val pairedDevices: Set<BluetoothDevice>? = bta!!.bondedDevices
// Try to guess.
for (device in pairedDevices!!) {
if (device.name.contains("obd")) {
elmConnect(device.name);
return;
}
}
// Still going therefore didn't find one therefore ask.
val cs: Array<CharSequence> = pairedDevices.map { z -> z.name }.toTypedArray()
var elmDeviceName: String = ""
val ab = AlertDialog.Builder(this);
ab.setTitle("Select device");
ab.setIcon(android.R.drawable.ic_dialog_alert);
ab.setPositiveButton("Select") { dialogueInterface, which ->
elmConnect(elmDeviceName);
};
ab.setNeutralButton("Cancel") { dialogueInterface, which ->
Toast.makeText(
applicationContext,
"Cancelled",
Toast.LENGTH_LONG
).show()
};
ab.setItems(cs) { dialog, which -> elmDeviceName = cs[which].toString() };
val a = ab.create();
a.setCancelable(false);
a.show();
}
Will this work with the local variable elmDeviceName being used to get the value of the selected item in the case when the OK button is pressed?

How can I get a value from a the ScalaFX thread?

I have a myNode: scalafx.scene.Node that I'd like to turn into a bitmap so that I can save it to disk, compare it to other bitmaps, etc. I think the correct code to turn it into a scalafx.scene.image.WritableImage is
val writableImg = new Scene { root = new Group(myNode) }.snapshot(null)
and then I should be able to turn that into a java.awt.image.RenderedImage with
val renderedImg = SwingFXUtils.fromFXImage(writableImg, null)
The problem, as you've probably realized, is that I have to run the code to get wrImg on the ScalaFX thread. There's a question here that explains how to return a value, but I'm not having any luck translating that to Scala. I tried this:
lazy val writableImg: WritableImage = {
val wrImg = new FutureTask(new Callable[WritableImage]() {
override def call(): WritableImage = {
new Scene { root = new Group(myNode) }.snapshot(null)
}
})
Platform.runLater(wrImg)
wrImg.get()
}
but the code just hangs and never completes. Can anyone provide an idiomatic Scala version and/or tell me why the code never returns?
If you just want to save the image to disk you can simply do it on the same thread avoiding complication passing the image around. Something like this will work:
Platform.runLater {
val node = new Circle {
centerX = 200
centerY = 200
radius = 50
stroke = Color.BROWN
strokeWidth = 2
fill = Color.DARKKHAKI
}
val jfxImage = node.snapshot(new SnapshotParameters(), null)
val bufferedImage = SwingFXUtils.fromFXImage(jfxImage, null)
val file = new File("./snapshot.png")
println("Writing snapshot to: " + file.getAbsolutePath)
javax.imageio.ImageIO.write(bufferedImage, "png", file)
()
}
The empty () to have closure returning Unit, so ScalaFX Platform.runLater is happy.
Update:
If you want to have a value from Platform.runLater the approach you suggested in your question should be in general fine. However, you want to make sure that you do not block the FX Application Thread. If you call Platform.runLater on FX Application Thread you will lock out, so you may want to have something like this
def makeSnapshot() = myNode.snapshot(new SnapshotParameters(), null)
val writableImg = if (Platform.isFxApplicationThread) {
makeSnapshot()
} else {
val futureTask = new FutureTask(new Callable[WritableImage]() {
override def call(): WritableImage = makeSnapshot()
})
Platform.runLater(futureTask)
futureTask.get()
}

How does Enumerate work in MonoTouch?

In MonoTouch I need to process each object in an NSSet. My attempt, using Enumerate, is as follows:
public override void ReturnResults ( BarcodePickerController picker, NSSet results )
{
var n = results.Count; // Debugging - value is 3
results.Enumerate( delegate( NSObject obj, ref bool stop )
{
var foundCode = ( obj as BarcodeResult ); // Executed only once, not 3 times
if ( foundCode != null )
{
controller.BarcodeScannedResult (foundCode);
}
});
// Etc
}
Although the method is invoked with three objects in results, only one object is processed in the delegate. I would have expected the delegate to be executed three times, but I must have the wrong idea of how it works.
Unable to find any documentation or examples. Any suggestion much appreciated.
You have to set the ref parameter to false. This instructs the handler to continue enumerating:
if ( foundCode != null )
{
controller.BarcodeScannedResult (foundCode);
stop = false; // inside the null check
}
Here is the ObjC equivalent from Apple documentation.
Or you could try this extension method to make it easier..
public static class MyExtensions {
public static IEnumerable<T> ItemsAs<T>(this NSSet set) where T : NSObject {
List<T> res = new List<T>();
set.Enumerate( delegate( NSObject obj, ref bool stop ) {
T item = (T)( obj ); // Executed only once, not 3 times
if ( item != null ) {
res.Add (item);
stop = false; // inside the null check
}
});
return res;
}
}
Then you can do something like:
foreach(BarcodeResult foundCode in results.ItemsAs<BarcodeResult>()) {
controller.BarcodeScannedResult (foundCode);
}
Note: Keep in mind this creates another list and copies everything to it, which is less efficient. I did this because "yield return" isn't allowed in anonymous methods, and the alternative ways I could think of to make it a real enumerator without the copy were much much more code. Most of the sets I deal with are tiny so this doesn't matter, but if you have a big set this isn't ideal.

Resources