Using generative code in ClojureDart
Introduction
One thing I want to experiment in ClojureDart is to use generative code from Dart.
One example of generative code could be retrofit
a package in Dart to configure a client http easily. We will make an example how to use this in ClojureDart during this article.
If you want a quick example of ClojureDart take a look of my previous article.
Adding Retrofit package
Take a look of Retrofit documentation where we can find how to install it, for quick install follow:
flutter pub add retrofit
flutter pub add json_annotation
flutter pub add dio
flutter pub add -d retrofit_generator
flutter pub add -d build_runner
Create the RestApi client
Create a file named api_client.dart
inside lib/
import 'package:dio/dio.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
part 'api_client.g.dart';
@RestApi()
abstract class RestClient {
factory RestClient(Dio dio) = _RestClient;
@GET("/albums")
Future<List<Album>> getAlbums();
}
@JsonSerializable()
class Album {
int? id;
String? title;
int? userId;
Album({this.id, this.title, this.userId});
factory Album.fromJson(Map<String, dynamic> json) => _$AlbumFromJson(json);
Map<String, dynamic> toJson() => _$AlbumToJson(this);
}
Generate the code
Now we will use the build_runner
we just install to generate the code for the client api, run this command:
dart pub run build_runner build
It's generate api_client.g.dart
, we now have everything we want in the dart part. Let's see how to use it in ClojureDart now.
Subscribe to Etienne Théodore
Use RestClient into ClojureDart
We will use previous example code to modify it and implement the retrofit part.
First the api/albums.cljd
:
(ns api.albums
(:require
["api_client.dart" :as api]
["package:dio/dio.dart" :as dio]))
(def ^:private base-url "https://jsonplaceholder.typicode.com/")
(def ^:private dio-client (dio/Dio (dio/BaseOptions .baseUrl base-url)))
(def ^:private api-client (api/RestClient dio-client))
(defn get-albums
"Call http for /albums or /albums/{page}
The argument page should be a number.
**Usage**
```clojure
:watch [response (get-album)];; You get a list of Album
```
"
([] (-> api-client
(.getAlbums))))
The important part here is the require, as you can see, to import local Dart file we need to ["api_client.dart" :as api]
by default ClojureDart looking in lib/
so we don't have to add it.
Then we create a def
for our base url. Then a def
to configure dio, it will be use by retrofit. Finally we create the api-client
with the api/RestClient
we created before.
Then get-albums
just call the (-> api-client(.getAlbums))
from the RestClient dart. The documentation for the ->
macro can be find here .
Now we have a list of Album
with a proper type. So to use it we can change some code in the main.cljd:
(ns acme.main
(:require
[api.albums :as api]
[pages.album_detail :as album-detail]
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
(defn- navigate [navigator page name]
(.push
navigator (#/(m/MaterialPageRoute Object)
.settings (m/RouteSettings .name name)
.builder
(f/build page))))
(defn- build-list-items [albums]
(f/widget
:get [m/Navigator]
(m/ListView.builder
.itemCount (count albums)
.itemBuilder
(f/build
#_{:clj-kondo/ignore [:unresolved-symbol]}
[idx]
(let [album (get-in albums #_{:clj-kondo/ignore [:unresolved-symbol]} [idx])]
(m/ListTile
.onTap (fn [] (navigate navigator
(album-detail/view album)
(str "/album-detail/" (.-id album))))
.title (m/Text (.-title album))))))))
(defn main []
(f/run
(m/MaterialApp
.title "Fetch Data List Example"
.theme (m/ThemeData .primarySwatch m/Colors.blue))
.home
(m/Scaffold .appBar (m/AppBar .title (m/Text "Fetch Data List Example")))
.body
(m/Center)
:watch [response (api.albums/get-albums)]
(if-some [{} response]
(build-list-items response)
(m/CircularProgressIndicator))))
We change the call to :watch [response (api.albums/get-albums)]
then we use it in the builder list (.-title album)
to get the title from the model Album for example.
In the detail view we change some code too:
(ns pages.album_detail
(:require
["package:flutter/material.dart" :as m]
[cljd.flutter :as f]))
(defn view [album]
(m/Scaffold
.appBar (m/AppBar .title (m/Text (str (.-id album))))
.body
(f/widget
:get [m/Navigator]
:get {{{name .-name} .-settings} m/ModalRoute}
m/Center
(m/Column
.mainAxisAlignment m/MainAxisAlignment.center
.children
[(m/Column
.crossAxisAlignment m/CrossAxisAlignment.start
.children [(m/Text name)
(m/Text (str "Title:" (.-title album)))
(m/Text (str "Id:" (.-id album)))
(m/Text (str "UserId:" (.-userId album)))])
(m/ElevatedButton .onPressed #(.pop navigator) .child (m/Text "Go back!"))]))))
Here we can see (.-userId album)
to get the userId from the Album
class.
Conclusion
And it's done, we have changed the api part to be more typed, and safe. And use local generated code thanks to retrofit. We can still change the baseUrl inside ClojureDart or other BaseOptions
settings. Well done !
Like always feel free to fork/clone/reuse the code I use for this article: