Jetpack compose for desktop: run application in background? - kotlin

i am new to the jetpack compose. I did a lot of research on that topic but i can't find anything useful. What i wanna achieve is that if I close my window, my application will stay in the background and can be opened again from the tray. I managed to create the tray, but when i close the application window it closes the whole application. How can i achieve that?
This application will be on Windows and MacOS only. I don't care about android right now

Edit: For version 0.4.0
I managed to figure it out. The main function should be an application, not the window
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
}
And if the application contains both the Window and the Tray, it will continue to run in the background and does not close after the window is closed.
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
Tray(
icon = BufferedImage(24, 24, 1),
menu = {
Item(
"Exit",
onClick = { exitProcess(1) }
)
}
)
Window{
Text("Hello World")
}
}
Edit: For version 1.0.0-beta5
Now you have to specify the onCloseRequest on the window object, if you leave it blank it won't close the window.
In the application, create a variable which will indicate whether the window is open or not.
Create the tray as before. The tray icon now requires a Painter object instead of a BufferedImage.
Than simply check if the window open state is true, show the window, otherwise do nothing.
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
val isOpen = remember { mutableStateOf(true)}
Tray(
icon = TrayIcon,
menu = {
Item(
"Exit",
onClick = { exitApplication() }
)
}
)
if(isOpen.value){
Window(
onCloseRequest = { isOpen.value = false }
) {
MaterialTheme {
Text("Hello World")
}
}
}
}
object TrayIcon : Painter() {
override val intrinsicSize = Size(256f, 256f)
override fun DrawScope.onDraw() {
drawOval(Color(0xFFFFA500))
}
}

Related

Android - Kotlin - How can I create an alert dialog when a view is clicked on a recycler view item?

I'm using the same code outside of the recycler view adapter and it works fine. But here nothing happens when I click on the view.
holder.edit.setOnClickListener {
if(context is MainActivity){
val mDialog = LayoutInflater.from(this).inflate(R.layout.add_ip_dialog, null)
val mBuilder = AlertDialog.Builder(this).setView(mDialog).setTitle("New Product Information")
val mAlertDialog = mBuilder.show()
mDialog.save_button_dialog.setOnClickListener {
mAlertDialog.dismiss()
}
mDialog.cancel_button_dialog.setOnClickListener {
mAlertDialog.dismiss()
}
}
}
I tried changing the "this" to "context" but nothing changed.
I used a button in a fragment and I use it like this.
imgBack.setOnClickListener {
val eBuilder = AlertDialog.Builder(context)
eBuilder.setTitle("Discard Note")
eBuilder.setIcon(R.drawable.ic_warning)
eBuilder.setMessage("Note is not Saved. Are you sure you want to discard note?")
eBuilder.setPositiveButton("Discard", fun(_: DialogInterface, _: Int) {
requireActivity().supportFragmentManager.popBackStack()
})
eBuilder.setNegativeButton("Cancel"){
_, _ -> eBuilder.setCancelable(true)
}
eBuilder.create()
eBuilder.show()
}
this should help and give a good example

Why does Jetpack Compose LazyColumn MutableLiveData only recompose on first button click?

Learning Kotlin and Jetpack Compose in Android Studio. Trying to understand recomposition.
The following code only recomposes on the first button click - why?
Output is shown below my code. Basically, the first time I click the Add New Item button the screen recomposes perfectly, but then it does not recompose again. However, if you look at the LogCat, you can see that it's still working, just not updating the screen. What am I missing?
View Model
class MainViewModel : ViewModel() {
val listLiveData: LiveData<List<Int>>
get() = newListLiveData
private val newList = ArrayList<Int>()
private val newListLiveData = MutableLiveData<List<Int>>()
fun addNewListItem(listItem: Int) {
newList.add(listItem)
newListLiveData.value = newList
println("**** addNewListItem($listItem)")
}
}
Home Screen Scaffolding and Content
#Composable
fun HomeScreen(
navController: NavHostController,
model: MainViewModel
) {
val stuff by model.listLiveData.observeAsState(emptyList())
Scaffold(
content = { Content(model, stuff) }
)
}
#Composable
fun Content(
model: MainViewModel = MainViewModel(),
stuff: List<Int>
) {
LazyColumn() {
items(items = stuff){ index ->
Text("$index")
}
}
Button(
onClick = { model.addNewListItem((0..10).random()) },
) {
Text(text = "Add Random Item to List")
}
}
LogCat Output
I/System.out: **** addNewListItem(0) <-- Recomposes fine here
I/System.out: **** addNewListItem(2) <-- Does not recompose
I/System.out: **** addNewListItem(5) <-- Does not recompose
I/System.out: **** addNewListItem(6) <-- Does not recompose
I/System.out: **** addNewListItem(4) <-- Does not recompose
Screenshot
Thank you
Honestly speaking, as long as you do not have to do with interoperability with the View system, AVOID using anything other than the built-for-Compose MutableState<T> objetcs. The ultra-boilerplate code of yours can simply be replaced by
ViewModel{
val cleanList by mutableStateListOf<Item>() // Initialized as an Empty list of 'Item's
fun addItem(item: Item){
cleanList.add
}
}
Within Composables,
#Composable
MyComposable(
list: List<Item>,
onAddItem: (Item) -> Unit // Receive Item, return Unit
) {
Button { // This is on-Click, assume
onAddItem(/*Add Item Here, per logic*/)
}
}
Call it anywhere like so
MyComposable(list = viewModel.cleanList, onAddItem = viewModel::addItem)
This is some fine code here. Easy to read, free of boilerplate, bafflingly beautiful and clean as a crystal.
Leverage the beauty of Compose, and ditch the ugly legacy crap.

How can I create an application that runs on my desktop toolbar? [duplicate]

i am new to the jetpack compose. I did a lot of research on that topic but i can't find anything useful. What i wanna achieve is that if I close my window, my application will stay in the background and can be opened again from the tray. I managed to create the tray, but when i close the application window it closes the whole application. How can i achieve that?
This application will be on Windows and MacOS only. I don't care about android right now
Edit: For version 0.4.0
I managed to figure it out. The main function should be an application, not the window
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
}
And if the application contains both the Window and the Tray, it will continue to run in the background and does not close after the window is closed.
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
Tray(
icon = BufferedImage(24, 24, 1),
menu = {
Item(
"Exit",
onClick = { exitProcess(1) }
)
}
)
Window{
Text("Hello World")
}
}
Edit: For version 1.0.0-beta5
Now you have to specify the onCloseRequest on the window object, if you leave it blank it won't close the window.
In the application, create a variable which will indicate whether the window is open or not.
Create the tray as before. The tray icon now requires a Painter object instead of a BufferedImage.
Than simply check if the window open state is true, show the window, otherwise do nothing.
#OptIn(ExperimentalComposeUiApi::class)
fun main() = application {
val isOpen = remember { mutableStateOf(true)}
Tray(
icon = TrayIcon,
menu = {
Item(
"Exit",
onClick = { exitApplication() }
)
}
)
if(isOpen.value){
Window(
onCloseRequest = { isOpen.value = false }
) {
MaterialTheme {
Text("Hello World")
}
}
}
}
object TrayIcon : Painter() {
override val intrinsicSize = Size(256f, 256f)
override fun DrawScope.onDraw() {
drawOval(Color(0xFFFFA500))
}
}

Jetpack Compose Desktop switch to new window

Hey I'm pretty new to Kotlin and am trying my hand at a GUI as my first small project.
For this I am using Jetpack Compose Desktop. I have already written a first small login window ( not the one in the GIF), and would like to open a new window with the "actual" content after logging in (not an external one, but in the same window).
Here is a video that may help you to understand what I mean:
(Not mine but thanks to Agon Mustafa - uplabs)
So that one continues with the registration in the same window and does not have to open a separate window for it. Hope you can help me:)
Here is an example of how to open and close multiple windows:
fun main() = application {
val applicationState = remember { MyApplicationState() }
for (window in applicationState.windows) {
key(window) {
MyWindow(window)
}
}
}
#Composable
private fun ApplicationScope.MyWindow(
state: MyWindowState
) = Window(onCloseRequest = state::close, title = state.title) {
MenuBar {
Menu("File") {
Item("New window", onClick = state.openNewWindow)
Item("Exit", onClick = state.exit)
}
}
}
private class MyApplicationState {
val windows = mutableStateListOf<MyWindowState>()
init {
windows += MyWindowState("Initial window")
}
fun openNewWindow() {
windows += MyWindowState("Window ${windows.size}")
}
fun exit() {
windows.clear()
}
private fun MyWindowState(
title: String
) = MyWindowState(
title,
openNewWindow = ::openNewWindow,
exit = ::exit,
windows::remove
)
}
private class MyWindowState(
val title: String,
val openNewWindow: () -> Unit,
val exit: () -> Unit,
private val close: (MyWindowState) -> Unit
) {
fun close() = close(this)
}
You can read more in the
tutorials
of Compose for desktop

TornadoFX: Error while updating label inside runAsync

I tried to implement a chat application using socket connection in Kotlin and TornadoFX library to make the GUI.
The problem comes when I try to launch the client because it keeps waiting a message from the Server although I put that code that update the label and receive the message inside a runAsync. I red the TornadoFX documentation and saw youtube videos but I cannot come to the solution.
I know that the issue is that the program is stuck in that block but can't figure how to do it.
class MyFirstView: View("Chat"){
var input: TextField by singleAssign()
var test = SimpleStringProperty()
val client: Client by inject()
init {
client.connect()
val t = thread(true) {
while (true) {
random = client.getMessage()
println(random)
Platform.runLater { test.set(random) }
}
}
}
override val root = vbox {
hbox {
label(test) {
bind(test)
}
}
hbox {
label("Write here some text")
input = textfield()
}
hbox {
button("Send") {
action{
client.writer.println(input.text)
}
}
}
}
}
You can only update UI elements on the UI thread, so if you want to manipulate the UI from a background thread, you need to wrap that particular code in runLater { }.
On another note, you shouldn't manipulate the text of the textfield or store ui element references with singleAssign. Instead you should bind your textfield to a StringProperty and manipulate the value instead. This is covered in the guide, so check it out :)