Using extensions in ClojureDart

Introduction

We love extensions in Dart, which help us reuse and apply some code to an existing class widget.

But how to use it in ClojureDart ? We'll see how to do it.

The result we will achieve in this article:

0:00
/

The Dart part

We still need some Dart code, for example, here we will create an extension to refresh a ListView.

Based on the last article we will change some code to apply for the extension but first, let's create the Dart file. We will use pull_to_refresh package for this widget:

Add it to your pubspec.yaml with the command flutter add pub pull_to_refresh then create the list_view_refreshable.dart:

import 'package:flutter/widgets.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';

extension ListViewRefreshable on ListView {
  Widget refreshable(Future<void> Function() onRefresh) => RefreshableWidget(
        builder: (refreshController) => SmartRefresher(
          controller: refreshController,
          onRefresh: () async {
            await onRefresh.call();
            refreshController.refreshCompleted();
          },
          child: this,
        ),
      );
}

class RefreshableWidget extends StatefulWidget {
  const RefreshableWidget({super.key, required this.builder});
  final Widget Function(RefreshController refreshController) builder;
  @override
  State<RefreshableWidget> createState() => _RefreshableWidgetState();
}

class _RefreshableWidgetState extends State<RefreshableWidget> {
  final RefreshController refreshController = RefreshController();

  @override
  Widget build(BuildContext context) {
    return widget.builder(refreshController);
  }
}
list_view_refreshable.dart

Here it's a simple extension to apply in every ListView a refreshable method.

Now let's come back to Clojure.

Subscribe to Etienne Théodore

Don’t miss out on the latest articles. Only one email by week about Flutter/Dart (no-spam)
your@email.com
Subscribe

Clojure Part

Let's require some stuff before we deep dive into the code, change the main.cljd to import the extension file:

(ns acme.main
  (:require
   [api.albums :as api]
   [pages.album_detail :as album-detail]
   ["list_view_refreshable.dart" :as ext-refresh]; our extension
   ["package:flutter/material.dart" :as m]
   [cljd.flutter :as f]))
main.cljd

Then we need a state to save the list of albums to perform the GET call.

(defonce ^:private app-state
  (atom {:albums []}))

Then we create the call function:

(defn- fetch-albums []
  (swap! app-state assoc :albums (await (api.albums/get-albums))))

This function will await the GET call from the API and then use the result to change the value of the app-state albums  thanks to swap! function and assoc.

Finally, we apply the refreshable function from the extension we have created:

(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))))))
       ext-refresh/ListViewRefreshable
       (.refreshable ; here the refresh
        (fn []
          (await (fetch-albums)))))))

The critical part here is the -> macro that allows us to use the .refreshable  extension just after the ListView. And of course the .refreshable itself to refresh the list via fetch-albums.

If you use VSCode you can extract some functions to refacto some parts of the code to be simpler to understand (on mac cmd+.).

For example, here we could extract this function to a named function like so:

(defn- list-view-albums [albums 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- build-list-items [albums]
  (f/widget
   :get [m/Navigator]
   (-> (list-view-albums albums navigator)
       ext-refresh/ListViewRefreshable
       (.refreshable ; here the refresh
        (fn []
          (await (fetch-albums)))))))
main.cljd

Conclusion

We saw how to use extensions in ClojureDart, with an example to pull to refresh any ListView. Happy coding!

Like always feel free to fork/clone/reuse the code I use for this article:

GitHub - Kiruel/fetch-data-list at extensions
ClojureDart example to fetch a simple list. Contribute to Kiruel/fetch-data-list development by creating an account on GitHub.