Opinions regarding Qt for Python for cross-platform desktop apps

The Memory consumption is cause of the Java JRE which is loaded for runtime. For a command line app I would always compile with native Image, for a GUI application as a Web App: if it would be for web we would speak about a Server and then it is small size also when speaking about mobile it is nativer compiled and also in a much lower size.

The speed up between compiled and JDK run is I would say 10x faster, the behavior with big data amounts JRE running is not beatable with any compiled one. Not python not C++ comes in the same area.

how can I compile a JavaFX app wirh native image? Can I deploy it to App Store?

ok I see that GraalVM or Liberica Native Image Kit could do that. I will test it out

Liberica Native Image Kit doesn’t include the HTML Viewer yet. And GraalVM has limitations too when talking about JavaFX, depending on what kind of libraries you are using under the hood. But again it is quite simple to generate with gradle packages for Windows, macOS, Linux. This one gives you an example fo one of my apps generating automatically a binary after running the “jpackage” task on a windows, macos or linux machine. Nothing to be changed on the script, it just needs to be run on the respective systems. You will get a pkg on macOS and an windows installer on Windows etc.

It even generates the online help with the build (vitepress) and uploads it. And it signs the macOS files with my certificates.

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.internal.os.OperatingSystem

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
}

// Loading all necessary plugins
plugins {
    id("java-library")

    id("com.dipien.releaseshub.gradle.plugin") version "4.0.0"

    // https://github.com/java9-modularity/gradle-modules-plugin
    id("org.javamodularity.moduleplugin") version "1.8.12"

    // https://github.com/openjfx/javafx-gradle-plugin
    id("org.openjfx.javafxplugin") version "0.0.14"

    // https://github.com/beryx/badass-jlink-plugin/
    id("org.beryx.jlink") version "2.26.0"

    // https://github.com/int128/gradle-ssh-plugin
    id("org.hidetake.ssh") version "2.11.2"
}

repositories {
    mavenLocal()
    mavenCentral()
}

apply(plugin: "java")

// Loading all necessary properties from gradle.properties file
// Java Version to use
targetCompatibility = appJavaVersion.toString()
sourceCompatibility = appJavaVersion.toString()

var currentOS = OperatingSystem.current()

// Project meta data
project.description = appDescription
project.ext.buildDate = new Date()
project.version = appVersion

run {
    jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
}

// Future dependencies to be added here
dependencies {
    implementation "org.apache.commons:commons-lang3:3.12.0"
    implementation "org.apache.poi:poi:5.2.3"
    implementation "org.apache.poi:poi-ooxml:5.2.3"
    implementation "org.apache.logging.log4j:log4j-core:2.20.0"
    implementation "com.univocity:univocity-parsers:2.9.1"
    implementation "org.controlsfx:controlsfx:11.1.2"
    implementation "org.hsqldb:hsqldb:2.7.2"


}

if (Os.isFamily(Os.FAMILY_MAC)) {
    remotes {
        webServer {
            host = "XXXXXXXXXXXX.de"
            user = "XXXXXXXXXXXX"
            identity = file("/Users/jmu/.ssh/id_rsa")
        }
    }
} else {
    remotes {
        webServer {
            host = "XXXXXXXXXXXX.de"
            user = "XXXXXXXXXXXX"
            identity = file("C:\\Users\\jmu\\.ssh\\id_rsa")
        }
    }
}

tasks.register("deployOnlineHelp") {
    doLast {
        ssh.run {
            session(remotes.webServer) {
                remove "/var/www/html/csv-converter.teccompanion.com/dist"
                put from: "${projectDir}/vitepress/.vitepress/dist/", into: "/var/www/html/csv-converter.teccompanion.com"
            }
        }
    }
}

// JavaFX dependencies
javafx {
    version = appJavaFxVersion.toString()
    // Read all Java FX modules from the properties file
    def mods = []
    for (mod in appJavaFxModules.split(",")) {
        mods.add(mod)
    }
    modules = mods
}

// Setting the environment
application {
    mainModule = appMainModule
    mainClass = appMainClass
}

// define a pre-build  task to build the vitePress docs beforehand
tasks.register('vitePress', Exec) {
    workingDir "${projectDir}/vitepress" // replace with your actual app path
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine "npm.cmd", "run", "docs:build"
    } else {
        commandLine "npm", "run", "docs:build"
    }
}

// make this task a dependency of the build and the run task
deployOnlineHelp.dependsOn(vitePress)
jpackage.dependsOn(deployOnlineHelp)

// JLink for modular projects
jlink {

    mergedModule {
        requires "java.xml"
    }
    // Some default options
    options.set(["--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages"])
    launcher {
        name = rootProject.name
            jvmArgs = ["-p", ".", "-Djdk.gtk.version=2", "-DFile.encoding=UTF-8"]
    }

    // Pack it!
    jpackage {
        //Resolve the used operating system
        targetPlatformName = ""
        if (currentOS.macOsX) {
            targetPlatformName = "mac"
        } else if (currentOS.linux) {
            targetPlatformName = "linux"
        } else if (currentOS.windows) {
            targetPlatformName = "win"
        }

        // Resource directory for native package overrides,
        resourceDir = file(appResources)

        java {
            toolchain {
                languageVersion = JavaLanguageVersion.of(appJavaVersion)
                modularity.inferModulePath.set(true)
            }
        }

        if (targetPlatformName == "mac") { // we are on mac
            installerType = "pkg" // we want to have macOS PKG
        }
        if (targetPlatformName == "win") { // we are on Windows - msi for msi installer
            installerType = "exe"
        }
        if (targetPlatformName == "linux") { // we are on linux
            installerType = "deb"
        }

        // Add jpackage-specific options
        installerOptions = ["--name", rootProject.name, // installer name
                            "--description", project.description,
                            "--copyright", appCopyright,
                            "--vendor", appVendor,]

        // Add platform-specific options for the target image and for jpackage
        if (installerType == "pkg") {
            imageOptions += ["--mac-sign", "--icon", appResources + "icon.icns",
                             "--mac-package-name", "CSV Converter",
                             "--mac-package-signing-prefix", "com.teccompanion.csvconverter.common",
                             "--mac-signing-key-user-name", "Jeannot Muller (XXXXXXXXXXXX)",
                             "--mac-signing-keychain", "/Users/jmu/Library/Keychains/login.keychain-db"]

            installerOptions += ["--license-file", appResources + "LICENSE-OS-Installer.txt",
                                 "--mac-package-identifier", "com.teccompanion.csvconverter.common",
                                 "--mac-sign",
                                 "--mac-package-name", "CSV Converter",
                                 "--mac-package-signing-prefix", "com.teccompanion.csvconverter.common",
                                 "--mac-signing-key-user-name", "Jeannot Muller (XXXXXXXXXXXX)",
                                 "--mac-signing-keychain", "/Users/jmu/Library/Keychains/login.keychain-db"]
        }
        if (installerType == "exe") {
            icon = appResources + "icon.ico"
            imageOptions += ["--icon", appResources + "icon.ico"]
            installerOptions += ["--resource-dir", appResources]
            installerOptions += ["--win-per-user-install", // Install only for current user
                                 // "--win-console", // Shows what Java outputs to the console
                                 //"--win-dir-chooser",
                                 "--win-menu",
                                 "--win-shortcut",]
        }
        if (installerType in ["deb", "rpm"]) {
            imageOptions += ["--icon", appResources + "icon_256x256.png"]
            installerOptions += ["--linux-menu-group",
                                 "Tools",
                                 "--linux-shortcut"]
        }
        if (installerType == "deb") {
            installerOptions += ["--linux-deb-maintainer", appEmailAdress]
        }
        if (installerType == "rpm") {
            installerOptions += ["--linux-rpm-license-type",
                                 "GPLv3"]
        }
    }
}

This is the vitepress generated online help

My knowledge of Mac and Python is poor. It appears that there is a Github example that has briefly assessed python with a few GUI packages for the Mac App store. Since you are familiar with Python, then this may be the direction to go. Here is the link to his Github project: GitHub - davidfstr/Python-in-Mac-App-Store: Barebones Python app that can be submitted to the Mac App Store.

Here are some comments:

Any app you submit to the Mac App Store must have a GUI. In Python there are a few libraries available to create a GUI:

  • Tkinter / Tk - Built-in to Python. Just works.
    • Poorly documented. Limited widget set.
    • Overhead: Nothing
  • PyObjC / Cocoa - Full bindings to the Cocoa frameworks.
    • Reasonably documented and maintained. (But requires you to learn Cocoa.)
    • Provides full access to all OS X native widgets.
    • Overhead: Nothing
  • ctypes / Cocoa - Low-level bindings directly to Cocoa frameworks.
    • Most direct way to access native Cocoa frameworks, albeit verbose.
    • Almost no online documentation for this approach.
      • The cocoa-python library exposes a tiny subset of Cocoa using ctypes. Reading its source code is illustrative.
    • Overhead: Nothing
  • wxPython / wxWidgets
    • Well documented. Lots of widgets.
    • Note: Cannot be submitted to Mac App Store due to wxPython’s reliance on deprecated QuickTime APIs.
    • Overhead: 38.7 MB uncompressed, 13.3 MB compressed
  • PySide / QT
    • Well documented. Unsure if maintained.
    • Note: The app created by py2app crashes with a segmentation fault. My guess is that the py2app needs a special “recipe” for PySide. I don’t feel like writing one myself.
    • Overhead: 16.6 MB uncompressed, 6.3 MB compressed (estimated)
  • PyQt4 / QT
    • Note: I am unable to build from source and no binary installers for OS X are available. Seriously don’t people test anything these days?

The above assessment leaves the following choices for Mac App Store apps:

  • Tkinter
  • PyObjC
  • ctypes

I haven’t succeeded on the Apple App Store yet, but since I don’t have any business needs, I’m not in a hurry. It’s possible in general, but Apple is complaining about functionality issues, and I’m currently not in the mood to deal with that. :wink:

If you are interested in my experience with the Windows Store, that was quite easy:

thank you for the pointers. however it seems that the list of gui frameworks is not updated. PySide6 for example (now Qt for Python) is very well maintained by Qt group and kept up to date. However I can’t find updated info if there any app submitted to the mac app store. I would need to figure it out

by the way, if I am not wrong, jpackage produces a native package only (including a JRE and and the Jar of your app) and not a compiled version of the app. Am I wrong?

correct bit code running in the JVM, but jpackage includes the JVM into your binary (if you want, my above gradle script does exactly that, including tree shaking, so it “compiles” only the JavaFX components you are acutally using) as such that a customer doesn’t need to have Java installed, or the customer can have installed a “wrong” JVM or JDK for your app, it will still work.

GraalVM will compile into native code, but it is depatable if this is something you really want, as you will loose one benefit of the JVM, the self optimization of the program and the ahead of time compilation of only those functions the users is really using, etc.

In regards of the memory consumption a lot has happend since I last worked with Java around the beginning of the century. If you wish you can finetune many parameters of your embeded JVM including the max. memory consumption etc. The “old days” that Java will just consume whatever is available on free memory are long gone. Jetbrains IDEs are a good example, where you can define yourself how much resources their IDEs should consume at max.

BTW, as this might be your next question. The whole binary size is approx. 50 MB, (including pkg, msi, installer), the UIs, database, program, resources … that’s reasonable im my eyes. Especially as I am currently working on another app, which is by far bigger and the size doesn’t increase much.

But again, I’m not an evangelist of anything. I learned TypeScript, NodeJS, Go, Rust after Xojo but from a one to one comparison from what is feasible with Xojo JavaFX comes to the closest IMHO if you want an easy toolset and workflow chain. Especially as I am more a fan to develop backends, the UIs are just a “must have” to satisfy my customers needs :wink: - Personally I could live with console apps :wink:

Yikes…

What should we expect? It’s similar to GO; if the chosen language requires a built-in “runtime,” that’s how it operates, and we must accept the consequences. However, the difference in binary size isn’t as significant if you use additional functions than just “Hello World.” Only the “basic size” is “impressive” for very small apps.

Use Rust for the smallest binaries possible, but be prepared to deal with the currently limited IDE “solutions” for Rust.

With Java, no one forces us to include the JVM in an app, but we must then explain the requirements to the end user. In contrast, with GO, you don’t have a choice; the interpreter is provided with each app - the same applies to Electron. One reason Tauri is leaner is that it doesn’t include the complete Chrome engine per application for visualizing our JS code. Electron includes the complete Chrome engine per application …

Ultimately, we return to the fact that cross-platform development always comes with a price: compromises!

However, Java has numerous optimizations, such as creating a custom JVM for JavaFX that includes only the necessary components / modules for a specific app, and this process can easily be automated. But, naturally, these details require diligent effort and enthusiasm to understand, and they can easily be overlooked when hastily developing a test app. Similarly, there are countless optimizations tips for Go.

Nonetheless, this debate is likely irrelevant for a Xojo user. Their IDE consumes 1 GB of space, and with plugins, it requires even more. Due to their unconventional approach, almost every developer needs multiple installations of the complete package in different versions.

What do 19 MB mean to a decade-long Xojo developer? Just kidding… :slight_smile:

1 Like

Thank you again for your opinions and information that let me better understand my options and make a choice.
I realize that JavaFX is very powerful and you can bend it to your own desire. But it is not straightforward (as I can see with the example code provided) and requires tuning.

To me, the closest alternative for a Xojo developer is Python: simple language (not so verbose as java) and everything is concise. Together with a GUI toolkit (WxPython or Qt for Python) and with their GUI designer, it will easily allow to design UI with drag and drop.

In my opinion is a very good compromise, even if not perfect. But I will follow JavaFX evolution as I believe would be better for bigger projects.

Python is no alternative but you will learn that faster when you use it.

Javafx is straight forward and in my opinion it is even good that this verbosity is given.

So for real applications you may not find anything what has a so perfected ecosystem. That’s the point using java.

1 Like

yup, if you don’t want any compromises, go for XCode on Apple devices, Visual Studio for Microsoft, etc. If you are lazy like me live with the compromises. I thought that has always been the reason to work with Xojo. Now that Xojo failed, JavaFX is really IMHO not only an alternative but even better in so many ways, but yes it is of course still a compromise to any “native” solution. Thank you once again for the suggestion.

I’m not an evangelist. Better than hopping from one solution to the next and not realizing anything, it is a better choice to stick with one tool and get things done. There are many options and everyone has different requirements. Verbosity? That was my mantra too, until I learned that every modern Java IDE is doing 80 percent of the boring work :slight_smile:

That’s true and in so many ways also really good. Using IntelliJ is doing that work behind the curtains. You can do it manually but…who wants that. For example Controllers for fxml Files…they are generated automatically with an installed plugin. That is a fast and nice helper. You can write manually. But Should you? No. There is no sense behind that. The IDE is exactly there for this Jobs. That`s it.

1 Like

exactly, only downside is the learning curve for an IDE. My above gradle script looks “complicated”, but only if you don’t know what gradle (or maven) is all about. Comparable to using nodeJS without learning how the package manager works. But here again the IDE is doing most of the job, beside AI or a simple copy & paste after some research. There is a learning curve no doubts, but once you have your script in place you won’t touch it much, if at all for any app you’ll write.

But that’s also something that Xojo doesn’t publicize very much. Software development has simply become more complex and you have to learn and master several techniques. There is also no magic button in Xojo that an app just ends up in the store.

My comment was unfair and I apologize.
Earlier in the year I re-wrote a 14 mb Xojo built helper into pure Objective-C. The Objective-C version was ~160k for both current architectures. It not only improved disk usage, reduced download times, but the obj-C version can do the task and return a result before the bloated Xojo helped has even completed launching, thus improving the performance of the app it was being used in.

Which are all things that ‘click’ with me as an improvement over Xojo, and something that I was trying to get Xojo to look into as an improvement. Granted, I can’t easily make the Obj-C app run on any other platform (even iOS).

I look forward to re-writing it once again, but this time in Swift, just to get a comparison there.

1 Like

There’s no reason to apologize to me, I didn’t feel offended at all!

I’m certainly not waging a JavaFX religious war. Why would I? I was also one of those who ridiculed Java (and @thorstenstueker ).

I’m more of a backend developer, the data slingshot who wants to connect systems, create something new from numerous data sources, and would rather export the result directly into PDFs or Office documents than visualize them in a UI beforehand.

After Xojo, I tried Node.js, then Go, then Rust (and Tauri). All three variants were great and better than Xojo in many aspects. I also like Python, but in my opinion, the numerous versions don’t always make deployment easy.

@thorstenstueker’s insistence frustrated me so much that I decided to give Java another try after 20 years. I quickly discovered that it had evolved significantly from what I remembered. Even lambdas are now available. I was also impressed by JavaFX in terms of both speed and functionality. When comparing it to Xojo, the latter seems inferior (to stay polite). If someone only develops for macOS or Windows and desires a “native” feeling in their apps, then by all means, use the platform-specific tools.

I don’t particularly care about the “native” aspect, as my customers are satisfied if they can simply use a program effectively. A user interface should be easy to use and visually appealing, but no one has ever questioned me about why my apps aren’t “native” or inquired about the size of the app. Perhaps administrators might, but they are more concerned with whether an app is easy to deploy rather than obsessing over its size. What is truly remarkable in the Java environment is the abundance of mature and free libraries, such as Apache POI, which outperforms any Xojo plugin by a significant margin.

What is indeed astonishing is the lack of promotion surrounding JavaFX. However, this is likely due to its clientele, consisting of numerous corporate users who don’t typically create YouTube videos quickly. Furthermore, the documentation is so comprehensive that one simply needs to read it to understand it :slight_smile:

Returning to the subject at hand, everyone should use what works best for them. The only challenge is that it takes time and experience to identify any dead ends. I began learning Java in July of last year. For the first six months, I remained skeptical and wouldn’t have dared to claim that I was ecstatic.

However, each individual must make that decision for themselves. I would venture to say that it’s wise to allow yourself some time before making a final decision on which tool to use in shaping your future. In my personal opinion, the LTS releases of Java(FX) hold a significant competitive advantage.

1 Like

Since there is windows and macos nobody asked me for native interfaces. Never.

How our two worlds are so different
I got, and still get, that kind of comment all the time

Gee that doesnt look right for …
Gee that doesnt seem to behave quite like …

About evenly split between macOS and Windows (really depending on which client it is)
NONE of my client use Linux for anything other than Web servers where the UI is all web based anyway

But, you write for a different markets than I do
I’m sure thats relevant
I’d guess that, depending on a persons audience, this is, or isnt, a concern

2 Likes