Claude
Skills
Sign in
Back

server-side-rendering-flutter-apps-with-rfw

Included with Lifetime
$97 forever

Learn how to build a dynamic Flutter app using Server Side Rendering (SSR) with the rfw package, enabling UI updates driven by server logic and binary data exchange via HTTP.

Design

What this skill does


# Server Side Rendering Flutter Apps with RFW


This post will guide you how to build a Flutter app that takes advantage of Server Side Rendering (SSR) and being able to update UI dynamically.

> If you are new to flutter you can follow this [post](https://rodydavis.com/posts/first-flutter-project) on getting started

This technique will use the [rfw](https://pub.dev/packages/rfw) package on the server and client to send binary data via HTTP requests.

> **TLDR** You can find the final source [here](https://github.com/rodydavis/flutter_ssr).

## Getting started 

Create a new directory called `flutter_ssr` and navigate to it in terminal or open it up in your favorite IDE.

## Approaches to updates 

If you are in the mobile world then you know how challenging it can be to get all your users on the latest version. Even just having an API or database schema can be very hard to update because of users on older versions (sometimes due to OS limitations).

### Code Push 

[Shorebird](https://shorebird.dev/) takes an interesting approach to delivering updates to the users via Code Push and will update the apps live. This does have an advantage since it will update the UI and logic but what if the content update was only intended for a specific user or set of users?

### Latest version supported 

Another approach is to simply have an SLO/Policy that you only support X number of recent releases and that the app will not work on older versions.

For something like this on mobile you would use [upgrader](https://pub.dev/packages/upgrader) via [AppCast](https://sparkle-project.org/about/) or [in\_app\_update](https://pub.dev/packages/in_app_update) to use Google Play APIs to update the app in the background or prevent using until updated.

This has an advantage to know that users will be on the latest or no be supported and allow you to target newer APIs and roll updates easier. This does mean that users will be frustrated by updates more and that older devices may not be supported.

### Server driven updates 

Not all apps need to render data on the server and sometimes when building an offline first application you want to do everything local first, but this is for when you need to build a server first application. Here are some examples and use cases:

*   Bank accounts
*   Airline / Hotel / Car booking
*   Chat applications (Instant messaging)
*   Marketing and AB testing
*   Database first applications

Each of these examples does not mean they are server only and in many cases you want to still cache the data locally to still offer a great offline experience.

With Flutter you are building a runtime that you are shipping to the user as a Single Page Application (SPA) on the web and a mobile/desktop app on the stores. This means you need to ship all the logic and UI for every update.

This has an advantage for doing more logic on the server and potentially really heavy requests are done server side and just the rendered UI is sent to the client. The client can still cache the response and allow for offline viewing too. These disadvantage here is that the client is expected to communicate with the server at some point and may not be suitable for offline only applications.

## Remote Flutter Widgets (RFW) 

The Flutter team has a package for creating widgets on the client and server and sending data necessary to connect them. This package is called [rfw](https://pub.dev/packages/rfw).

> While it is possible to ship logic in addition to UI as WASM that is out of scope for this post

The rfw package uses a text format that can be compiled to binary and be used to represent state and dispatch events.

```
import core;
import material;

widget MaterialShop = Scaffold(
  appBar: AppBar(
    title: Text(text: ['Products']),
  ),
  body: ListView(
    children: [
      ...for product in data.server.games:
        Product(product: product)
    ],
  ),
);

widget Product = ListTile(
  title: Text(text: args.product.name),
  onTap: event 'shop.productSelect' { name: args.product.name, path: args.product.link },
);
```

Multiple widgets can be defined and it may even look similar to the Dart API you are used to in Flutter but it is not quite the same.

There are not logic branching blocks or conditional rendering but rather is a stateless format capable of updating every frame if needed.

Take the following Flutter counter app that is generated when you create a new project:

```
class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

We could represent that in rfw like this:

```
import widgets;
import material;

widget root = Scaffold(
  appBar: AppBar(
    title: Text(text: ['Counter Example']),
    centerTitle: true,
    backgroundColor: data.colorScheme.inversePrimary,
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: "center",
      children: [
        Text(text: ["You have pushed the button this many times:"]),
        Text(
          text: [data.counter.value],
          style: {
            fontSize: 20.0,
          },
        ),
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
     onPressed: event "click" {},
     tooltip: ["Increment"],
     child: Icon(
        icon: 0xe047,
        fontFamily: 'MaterialIcons',
     ),
  ),
);
```

You may have noticed some arrays for strings and different ways of defining widgets. That is by design and the API will not be 100% with the Flutter SDK, but with the limitation comes different tradeoffs.

You can define any custom widgets in your application and only uses the UI you have defined in your design system and the server will only be able to generate UI that you expect.

It is also possible to create all the UI in the text format with rows, columns, containers and more.

## Setting up the server 

For this example we will be using [dart\_frog](https://dartfrog.vgv.dev/docs/overview) to create the server application.

In the directory that you created earlier run the following commands:

```
dart_frog create server
flutter pub add rfw
```

This will generate the server boilerplate for us and add the correct dependencies.

> Feel free to delete the test directory for now or update it later to check for the correct response.

Navigate to `/server/routes/index.dart` and update the file with the following:

```
import 'package:dart_frog/dart_frog.dart';
import 'package:rfw/formats.dart';

Response onRequest(RequestContext context) {
  var count = context.request.headers['COUNTER_VALUE'] ?? '0';

  if (context.request.method == HttpMethod.post) {
    count = (int.parse(count) + 1).toString();
  }

  return Response.bytes(
    body: encodeLibraryBlob(parseLibraryFile(template)),
    headers: {'COUNTER_VALUE': count},
  );
}

const template = '''
import widgets;
import material;

widget root = Scaffold(
  appBar: AppBar(
    title: Text(text: ['Counter Example']),
    centerTitle: true,
    backgroundColor: data.colorSchem
Files: 1
Size: 82.3 KB
Complexity: 38/100
Category: Design

Related in Design