# Flutter & ClojureDart

> Published  Jan 01 0001, last updated Dec 12 2025  
> By Ryan Fleck <hello@my-name-dot-ca> and written without LLMs!  
> Original manual at <https://manuals.ryanfleck.ca/flutter/>  
> Incredible writing of astonishing quality and insight - Happy Hacking!


# What is Flutter? {#what-is-flutter}

> Flutter is an open source framework for building beautiful, natively
> compiled, multi-platform applications from a single codebase.

-   Read the [Flutter docs](https://docs.flutter.dev/get-started/learn-flutter)

Flutter is all about building **widget trees**, with out-of-box and custom
widgets. Widgets are objects based on classes, which can be extended.


# What is ClojureDart? {#what-is-clojuredart}

> ClojureDart is a recent Clojure dialect to make native mobile and
> desktop apps using Flutter and the Dart ecosystem.

-   Read the [ClojureDart quick start](https://github.com/Tensegritics/ClojureDart/blob/main/doc/quick-start.md)
-   Read the [ClojureDart + Flutter quick start](https://github.com/Tensegritics/ClojureDart/blob/main/doc/flutter-quick-start.md)


# Installation {#installation}

Developing a Flutter app with ClojureDart involves installing Java,
Clojure, Android Studio, XCode (on Mac), and finally Flutter. Mobile
development is complex and requires large, complex toolchains.

-   [Windows](https://docs.flutter.dev/get-started/install/windows/mobile) (use **choco**)
-   [GNU/Linux](https://docs.flutter.dev/get-started/install/linux/android) (use **asdf**)
-   [MacOS](https://docs.flutter.dev/get-started/install/macos/mobile-ios) (use **brew**)

...then follow the [ClojureDart Flutter quickstart](https://github.com/Tensegritics/ClojureDart/blob/main/doc/flutter-quick-start.md).

On GNU/Linux you will also need the \`ninja-build\` apt package.


# The Developer Experience Factor (DevX) {#the-developer-experience-factor--devx}

When evaluating cross-platform mobile app development systems for a
side project, I had an eye out for three key features to keep my
sanity:

1.  A short and simple toolchain
2.  Easy to access and develop with hardware features (bluetooth)
3.  Leverage my existing React knowledge

{{< figure src="/images/cljd/IMG20241117194238-min.jpg" >}}

After evaluating ****React Native**** (+ClojureScript) and ****Flutter****
(+ClojureDart) it became abundantly clear that Flutter had a much
shorter and more stable toolchain, significantly less complex
dependency management (JS can be hellish, things break often) and a
more consistent experience across platforms.

{{< figure src="/images/cljd/IMG20241117191207-min.jpg" >}}

At first glance, Flutter with ClojureDart seems to be a vastly
superior way for a solo dev to develop mobile apps. Time will tell if
this is true or a large misstep.


# Why Use an Intermediate Language? {#why-use-an-intermediate-language}

To reduce the lines of code required to produce a mobile app. Luckily
the [DartClojure](https://github.com/D00mch/DartClojure) project exists to convert dart code to ClojureDart,
it's built into Calva, and the [dartclojure.el](https://github.com/burinc/dartclojure.el?tab=readme-ov-file) Emacs package exists to
facilitate easy use.


# Notes on the Fundamentals {#notes-on-the-fundamentals}

These are rough, personal notes - read with caution.


## Hello World {#hello-world}

-   Every Flutter app must start with `runApp()`.
-   There is `void main(){}` to start the app.
-   The runApp function accepts a **widget tree**. There is no GUI UI
    builder - everything is done in code. Hooray!

<!--listend-->

```dart
import 'package:flutter/material.dart';

void main(){
  runApp(MaterialApp());
}
```

-   Instructors are using **screencast mode** in VS Code to help observers
-   Flutter has `pubspec.lock` which holds package info and **dependencies**

Here's an extremely basic hello world in **dart**:

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.deepPurple,
        body: Text('Hello World!'))));
}
```

Hit **debug** in VS Code to run the app.

Here's the **exact equivalent** in clojure-dart:

```clojure
(ns acme.main
  (:require ["package:flutter/material.dart" :as m]
            [cljd.flutter :as f]))

(defn main []
  (f/run
   (m/MaterialApp
    .title "Welcome to Flutter")
    .home
    (m/Scaffold .backgroundColor m.Colors/deepPurple)
    .body
    (m/Text "Let's get coding! Yahoo!")))
```

Run `clj -M:cljd flutter` to run the app.

See [main.cljd](~/Documents/Flutter_Experiments/clojure-dart/src/acme/main.cljd) in the test project. Hopefully this makes it clear how
named arguments are represented in ClojureDart versus plain Dart.

A little more example:

```clojure
(ns acme.main
  (:require ["package:flutter/material.dart" :as m]
            [cljd.flutter :as f]))

;; clj -M:cljd flutter

(defn main []
  (f/run
   (m/MaterialApp .title "Welcome to Flutter")
    .home
    (m/Scaffold .backgroundColor m.Colors/deepPurple)
    .body
    (m/Container
      .decoration (m/BoxDecoration
                    .gradient (m/LinearGradient
                                .colors [m.Colors/red m.Colors/white]
                                .begin m.Alignment/topLeft)))
    (m/Center)
    (m/Container .decoration (m/BoxDecoration .color m.Colors/red))
    (m/Text "Let's get coding! Yahoo!")))
```


## Classes, Widgets, Constructor Functions {#classes-widgets-constructor-functions}

Typically you want to **break up huge widget trees** by creating your own
widgets. Here's an example of that refactoring:

```dart
void main() {
  runApp(MaterialApp(
    home: Scaffold(
      backgroundColor: Colors.deepPurple,
      body: Container(
        decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.blue, Colors.red],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            )),
        child: const Center(
            child: Text("Hello World!",
                style: TextStyle(color: Colors.white, fontSize: 30)))),
  )));
}
```

Widgets are objects, and creating a new widget is the same as
**instantiating** a new object from a class.

```dart
void main() {
  runApp(MaterialApp(
      home: Scaffold(
       backgroundColor: Colors.deepPurple,
       // Replace the body with our new widget
       body: GradientContainer()
  )));
}

class GradientContainer extends StatelessWidget {
  // MISSING: Constructor Function, see below

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: // ... //
        child: const Center(child: Text("Hello World!",
                style: TextStyle(color: Colors.white, fontSize: 30))));
  }
}
```

The constructor function defines the data that must be passed to our
widget.

-   Optional args wrapped in `[]`
-   Default values assigned with `=`
-   Use `required` to ensure named parameters are passed

<!--listend-->

```dart
// Positional arguments (required by default)
const GradientContainer(a, b, [c, d=5]);

// Named arguments
const GradientContainer({a, required b, c=3});

// Necessary scaffolding
const GradientContainer({key}): super(key: key);

// Shortcut for the above
const GradientContainer({super.key});
```

**You can add multiple constructor functions to one class.** By providing
multiple constructors with different defaults, you can easily provide
shortcuts to instantiating slight variations on your widget.

```dart
GradientContainer.red({super.key, required this.children}) : colors = [Colors.deepOrange, Colors.red];
```

**Note:** To organize your project, move your widgets to
 `lowercase_name.dart` files. _This is convention._ They can then be
 imported into your main view with a line like the following, using
 your project name:

```dart
import 'package:your_flutter_app/gradient_container.dart';
```

While we are discussing conventions, here are a few:

```dart
ClassNames      // Classes start with an uppercase
variableNames   // Variables start with a lowercase

// Ending a variable with '?' allows it to be null
var Alignment startAlignment?;
```

`const` means something is a compile-time constant and will not change.
You cannot use const to 'lock' a widget that depends on variables.

**Pass data into classes** with the constructor function:

```dart
class GradientContainer extends StatelessWidget {
  const GradientContainer({super.key, required this.children});

  // 'final' means single-assignment
  final List<Widget> children;

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: const BoxDecoration(
            gradient: LinearGradient(
          colors: [Colors.blue, Colors.red],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        )),
        child: Center(
            child: c.Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: children)));
  }
}

// Usage:
GradientContainer(children: [
  Text("Hello World!")
])
```


## Functions are also just objects {#functions-are-also-just-objects}

```dart
(){} // Can be used inline

void rollDice(){}
```


## Stateful vs Stateless Widgets {#stateful-vs-stateless-widgets}

Long story short: Stateless should be used for elements that **will not
change** during their rendering lifetimes.

**Flutter will only update the UI if the `build` method is executed again.**

Within a stateful widget, the `setState` special function must be used
to prompt a UI update.

> "Calling setState notifies the framework that the internal state of
> this object has changed in a way that might impact the user interface
> in this subtree." ([docs](https://api.flutter.dev/flutter/widgets/State/setState.html))

```dart
import 'package:dchs_flutter_beacon/dchs_flutter_beacon.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart' as c;
import 'package:dice_roller/gradient_container.dart';
import 'package:flutter/services.dart';
import 'dart:math';

class DiceRoller extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return _DiceRollerState();
  }
}

// Instantiate objects outside your classes
final randomizer = Random();
final beacon = DchsFlutterBeacon();

class _DiceRollerState extends State<DiceRoller> {

  var activeDiceImage = 1;

  String getDiceImagePath(int a){
    return 'assets/images/dice-$a.png';
  }

  void rollDice() async {
    print("Rolling dice...");
    HapticFeedback.heavyImpact();
    SystemSound.play(SystemSoundType.alert);

    // setState prompts a UI update inside this StatefulWidget
    setState(() {
      activeDiceImage = randomizer.nextInt(6) + 1;
      print("Rolled a $activeDiceImage");
    });
  }

  @override
  Widget build(BuildContext context) {
    return GradientContainer.red(
        children: [

          // A button as a child
              TextButton(
                onPressed: rollDice, // Onpressed can run a function
                style: TextButton.styleFrom(
                padding: EdgeInsets.all(20),
                foregroundColor: Colors.white,
                textStyle: const TextStyle(fontSize: 28)
                ),
              child: const Text("Roll Now"))
        ]);
  }
}

```


## Images and assets {#images-and-assets}

You may include assets organized however you want in your project, but
they must be mentioned in your `pubspec.yaml` file like so:

```yaml
flutter:
  # To add assets to your application, add an assets section:
  assets:
    - assets/images/example-1.png
    - assets/images/example-2.png
```

These can be included like so:

```dart
Image.asset('assets/images/example-2.png')
```


## Futures and Async/Await {#futures-and-async-await}

<https://dart.dev/libraries/async/async-await#example-asynchronous-functions>


## Package Management {#package-management}

In the project root you can run something like this:

```bash
flutter pub add dhcs_flutter_beacon
```

To add a library like [dhcs_flutter_beacon](https://pub.dev/packages/dchs_flutter_beacon) ([gh](https://github.com/dariocavada/dchs_flutter_beacon)) to your project.


## SQLite {#sqlite}

Per this [flutter cookbook article](https://docs.flutter.dev/cookbook/persistence/sqlite), you can store and retrieve data
from SQLite with the `sqflite` package.

```bash
flutter pub add sqflite path
```


## Reading and Writing Files {#reading-and-writing-files}

Per the flutter cookbook article '[reading and writing files](https://docs.flutter.dev/cookbook/persistence/reading-writing-files)', use the
`path_provider` and `file_picker` package on mobile to provide this. The
packages should automatically ask for permissions when they are required.

-   [file_picker](https://pub.dev/packages/file_picker)
-   [path_provider](https://pub.dev/packages/path_provider)

<!--listend-->

```dart
String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
```


## User Preferences {#user-preferences}

Automatically wraps platform-specific user data storage to keep
key-value pairs.

-   [shared_preferences](https://pub.dev/packages/shared_preferences)



> Thank you for reading!  
> Find more content at <https://manuals.ryanfleck.ca/>  
> Source page: <https://manuals.ryanfleck.ca/flutter/>  
> Site index: [llms.txt](https://manuals.ryanfleck.ca/llms.txt)