Written by Shalitha Suranga✏️
Flutter is a popular open source framework for creating cross-platform applications for release on Android, iOS, Linux, macOS, Windows, Fuchsia, web, and others. Flutter is growing more popular every day because of its performance and inbuilt platform-independent UI widgets.
Go is a compiled, statically typed, high-performance language with a simple syntax.
Flutter and Go became popular in the developer community after Google initiated both open source projects. Nowadays, many developers choose to use Go and Flutter together. Using a Go backend for a Flutter frontend specifically brings many advantages.
In this article, we’ll discuss these advantages and verify them practically by building a full-stack application.
- Advantages of using a Go backend for Flutter apps
- Tutorial: Building a Flutter app with a Go backend
- Reusing Go code in the Flutter app
Advantages of using a Go backend for Flutter apps
Application frontends usually communicate with the server-side modules with various network communication concepts, such as REST, WebSocket, SOAP, and gRPC.
The above communication concepts are tech stack-agnostic, so the backend technology doesn’t affect the frontend and vice versa. However, Go-based backends bring numerous nontechnical and hidden benefits for Flutter frontends. Further, you can avoid time-consuming business logic rewrites by directly using Go modules in the Flutter app.
Community, popularity, and trends
Both Go and Flutter are Google open source projects. The Google open source community backs both projects by offering free community-based developer support, contributing code, and creating resources. You can discuss your Go development problems in the official Go mailing thread and Flutter-related problems in the official Flutter mailing list.
Google released Go v1 in 2012 and introduced Flutter v1 in 2018, but both technologies experienced rapid growth for cloud-based business applications in late 2019. Both projects are now growing in popularity every day and come with an excellent reputation, community support, and up-to-date, Google-engineered technology.
Go and Flutter are performance-first technologies
Nowadays, some developers tend to overlook application performance due to powerful computer hardware components. For example, many individuals use powerful computers and mobile devices, so hybrid apps don't show performance issues despite those apps generally performing slower than native apps. Additionally, many web developers rarely need to optimize web backend performance due to strong cloud computing infrastructure.
Both Go and Flutter projects strive to solve the primary technical problem by carefully considering performance factors.
Flutter offers near-native performance with a rendering canvas powered by Skia and the native platform channels concept.
The Go compiler produces fast and optimized native binaries and makes Go quick and agile, similar to other modern, popular, enterprise-level programming languages like C#, Java, and JavaScript (Node.js).
A Go backend offers fast and efficient native server-side services for Flutter applications to achieve better native performance.
Similarities in development environment
Flutter uses Dart as the cross-platform application development language. Dart and Go offer features to solve different technical problems. However, Go/Dart syntax, developer tools, and third-party libraries have considerable similarities. Therefore, the same full-stack development team can work on both backend and frontend projects without any productivity issues. Flutter developers can also get started with Go backend development comfortably thanks to Go’s minimal syntax.
Moreover, Go development tools work perfectly on all Flutter development tools’ operating systems. As a result, you can configure a productive Go development environment on your Flutter development computer.
Reusing Go backend logic in Flutter
Sometimes we have to reuse backend code directly in a frontend application. If you use Node.js for the backend and React Native for the frontend, you can easily share common business logic by creating a JavaScript package.
However, Dart’s server-side support is still growing and is not comparable with the Go ecosystem yet, so you need to think twice before using Dart for writing the backend.
If you use C#, Java, or Node.js for developing your backend, you may have to rewrite the same existing business logic in Dart on the Flutter frontend. The Go mobile project offers a way to call Go code from the platform-specific mobile development environments, i.e., Java and Android.
Therefore, we can connect Go mobile with Flutter and build an efficient way to reuse Go-based business logic.
Using Go for serving the Flutter web app
As you may already know, users can access the Flutter app from a web browser with Flutter web support. But how can you serve your Flutter web app from your cloud environment? You either need to use a prebuilt static server or write one with a preferred backend language.
It’s possible to write a fast and complete static file server in Go with a few lines of code. You can even serve your Flutter web app from the Go RESTful backend without many configurations.
Tutorial: Building a Flutter app with a Go backend
Now that we know the benefits of using a Go backend for a Flutter application , let’s develop a Go RESTful web service and a Flutter frontend to verify the above advantages. We are going to build a full-stack product list application with a Go REST API and Flutter frontend. The Go-based RESTful web service will return a product list in JSON format and the Flutter app will display a product list by calling the web service.
We will also convert the Flutter app into a web app and serve it using the same RESTful web service. Finally, I will demonstrate how to share Go code with the Flutter app by using the Go mobile project.
Developing a Go RESTful backend
We are going to create a RESTful backend to produce a JSON-formatted product list. First, install the latest Go development tools if your computer doesn’t have the Go compiler. Next, create a new Go module with the following commands to get started:
mkdir go_backend
cd go_backend
go mod init go_backend
We need an HTTP routing library for developing RESTful web services. The Gin web framework offers almost all HTTP-based backend development features, such as routing, JSON binding, and validation. Add the Gin framework package to the current project with the following command:
go get -u github.com/gin-gonic/gin
We also need to enable CORS since we are going to use Flutter web in this tutorial. Download the Gin CORS middleware package into your project with the following command:
go get github.com/gin-contrib/cors
Now, create a file named main.go
and add the following code:
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
)
type Product struct {
Id int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
Description string `json:"description"`
}
func productsHandler(c *gin.Context) {
products := []Product {
Product {100, "BassTune Headset 2.0", 200, "A headphone with a inbuilt high-quality microphone"},
Product {101, "Fastlane Toy Car", 100, "A toy car that comes with a free HD camera"},
Product {101, "ATV Gear Mouse", 75, "A high-quality mouse for office work and gaming"},
}
c.JSON(200, gin.H{
"products": products,
})
}
func main() {
r := gin.Default()
r.Use(cors.Default())
r.GET("/products", productsHandler)
r.Run(":5000")
}
The above code implements the GET /products
endpoint for returning a JSON-formatted product list. Here we construct a static product list by creating a Product
struct slice with the [] Product
syntax. We use a hardcoded product list for demonstration purposes, but you can use any preferred database connection to fetch stored product details.
Testing the Go backend
Let’s test the above Go backend. First, start the web service with the following command:
go run main.go
The above command starts the Gin RESTful server for accepting HTTP requests from the port 5000
. You can test the product list endpoint with the well-known Postman tool, as shown below.
Creating the application frontend with Flutter
Let’s create a product list with Flutter and display data from the above Go backend. If you haven’t already installed Flutter, you can easily do so from the official Flutter binary releases page.
First, create a new Flutter application with the following command:
flutter create flutter_frontend
Once the project is created, run it with the flutter run
command and test it on Chrome or your mobile device to verify that everything works as expected. We need to create Dart classes for each primary JSON object to make the codebase maintainable and readable. Add the following code to the lib/product_model.dart
file to define the product model:
class Product {
final int id;
final String name;
final String description;
final int price;
const Product({
required this.id,
required this.name,
required this.description,
required this.price,
});
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'],
name: json['name'],
description: json['description'],
price: json['price']
);
}
}
Next, we can create a Dart service to communicate with the Go backend. We will use the Dio HTTP client library, so add it to your Flutter project with the following command:
flutter pub add dio
Now, create the product service implementation in the lib/product_service.dart
file with the following Dart source:
import 'package:dio/dio.dart';
import 'package:flutter_frontend/product_model.dart';
class ProductService {
final String productsURL = 'http://localhost:5000/products';
final Dio dio = Dio();
ProductService();
Future<List<Product>> getProducts() async {
late List<Product> products;
try {
final res = await dio.get(productsURL);
products = res.data['products']
.map<Product>(
(item) => Product.fromJson(item),
)
.toList();
}
on DioError catch(e) {
products = [];
}
return products;
}
}
Here, we created the getProducts
asynchronous function to get products as instances of the Product
model by calling the Go backend via the Dio client. The above source code offers business data via a Dart service, but if you work with many HTTP endpoints you can use the repository pattern to organize the code better.
Finally, we can create the product list application frontend by importing the above product service. Replace the existing code in the lib/main.dart
file with the following code:
import 'package:flutter/material.dart';
import 'package:flutter_frontend/product_service.dart';
import 'package:flutter_frontend/product_model.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final _productService = ProductService();
@override
Widget build(BuildContext context) {
const title = 'Product List';
return MaterialApp(
title: title,
theme: new ThemeData(scaffoldBackgroundColor: const Color(0xffdddddd)),
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: FutureBuilder<List<Product>>(
future: _productService.getProducts(),
builder: (context, snapshot) {
var products = snapshot.data ?? [];
if(!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
var product = products[index];
return ListTile(
title: Text(products[index].name),
subtitle: Text('#${product.id} ${product.description}'),
trailing: Text('\${product.price}')
);
},
);
},
),
),
);
}
}
In the above code snippet, we used the FutureBuilder
class to display a loading animation until the frontend fetches all products from the backend. Run the application on your mobile device or Chrome by entering the flutter run
command. Make sure to replace localhost
with your computer’s local IP address if you are running the app on a Flutter device other than Chrome.
You will see the working product list application interface, as shown below.
Serving the Flutter web app with Go
Now we are going to expose our Flutter application as a web app via the Go backend . Then, we can access the app with any modern web browser. We can easily add static file serving support to the existing web service via the Gin static middleware. Install the static middleware package from the project directory.
go get github.com/gin-contrib/static
Next, add the following package import to the main Go source file.
"github.com/gin-contrib/static"
Finally, ask the Gin framework to serve static web content with the following code line:
r.Use(static.Serve("/", static.LocalFile("./static", false)))
Make sure that the final web service source looks like this:
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/static"
)
type Product struct {
Id int `json:"id"`
Name string `json:"name"`
Price int `json:"price"`
Description string `json:"description"`
}
func productsHandler(c *gin.Context) {
products := []Product {
Product {100, "BassTune Headset 2.0", 200, "A headphone with a inbuilt high-quality microphone"},
Product {101, "Fastlane Toy Car", 100, "A toy car that comes with a free HD camera"},
Product {101, "ATV Gear Mouse", 75, "A high-quality mouse for office work and gaming"},
}
c.JSON(200, gin.H{
"products": products,
})
}
func main() {
r := gin.Default()
r.Use(cors.Default())
r.Use(static.Serve("/", static.LocalFile("./static", false)))
r.GET("/products", productsHandler)
r.Run(":5000")
}
We can now build the Flutter web app to get static web resources. Enter the following command and generate Flutter web app resources:
flutter build web
Create a new directory named static
inside the Golang project and copy all generated Flutter web app resources from the ./build/web
directory to the static
directory.
Start the Go backend server and go to the http://localhost:5000
URL from your web browser. You will see the working Flutter web application, as shown below.
You can deploy the Go project into your cloud environment with Flutter web resources by using a container system like Docker. Then everyone can access your full-stack Flutter web app from web browsers.
Reusing Go code in the Flutter app
The Go mobile project offers tools to generate native Android and iOS libraries from Go source files. The Flutter project uses platform-specific host applications, aka embedders, to initialize the Flutter engine on each platform. Therefore, we can use Go modules in Flutter with the Go mobile project and Flutter platform channel APIs. This approach is helpful for developers who want to reuse Go backend code within the Flutter app without rewriting Go modules in Dart.
Now, we are going to modify the well-known Flutter demo application by displaying a random number instead of incrementing the existing number with each tap on the floating action button. We will generate this random number via a Go module. In the following example, I will explain how to embed a Go module in an Android app. You can use a similar approach to embed Go modules in iOS apps, too.
Before you continue with the tutorial, make sure that your computer has the following components, which can be easily installed via Android Studio:
- Android SDK
- Android NDK
- Clang compiler and Make
First, we need to install the Go mobile CLI with the following command:
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
If the gomobile
command doesn’t work after the installation process , you can solve the issue by adding the Go mobile binary to the PATH
environment variable, as shown below.
export PATH=$PATH:~/go/bin
Let’s create a new Go module to generate a random number. First, create a new Go project in your working directory.
mkdir gomobilelib
cd gomobilelib
go mod init gomobilelib
Next, create a new file named gomobilelib.go
and add the following source code.
package gomobilelib
import "math/rand"
type (
GoMobileLib struct {}
)
func (p *GoMobileLib) RandomNumber() int {
return rand.Intn(100);
}
We can generate native platform-specific libraries for each mobile operating system with Go mobile’s binding feature. Before using it, we need to install the Go mobile bind package.
go get golang.org/x/mobile/bind
Now we can generate an Android library file using the following command:
gomobile bind --target android
After running the above command, you can see the gomobilelib.aar
file in the Go module directory. The .aar file contains the Go runtime libraries and the above gomobilelib
module in platform-specific binary format for each mobile CPU architecture.
Let’s use the Android library by creating a new Flutter project. Create a new Flutter project with a Java-based host app.
flutter create gomobilefrontend -a java
Copy the gomobilelib.aar
file to the ./gomobilefrontend/android/app/src/main/libs
directory. Link the newly created library with the Android host app by adding the following configuration to the ./gomobilefrontend/android/app/build.gradle
file.
repositories {
flatDir{
dirs 'src/main/libs'
}
}
dependencies {
api(name:'gomobilelib', ext:'aar')
}
Next, replace the existing code in the MainActivity.java
file with the following code:
package com.example.gomobilefrontend;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import gomobilelib.GoMobileLib;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "example.com/gomobilelib";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GoMobileLib goMobileLib = new GoMobileLib();
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if(call.method.equals("getRandomNumber")) {
result.success(goMobileLib.randomNumber());
}
else {
result.notImplemented();
}
}
);
}
}
The above code exposes the Android library’s randomNumber
function as getRandomNumber
to the Flutter app via the platform channels API. Now we can invoke getRandomNumber
from the Flutter app to receive a new random number.
You can now create an asynchronous Dart function to call the exported Android library function. For example, the following Dart function updates the _counter
variable with the random number generated by the Go module:
static const platform = MethodChannel('example.com/gomobilelib');
int _counter = 0;
Future<void> _getRandomNumber() async {
int randomNumber;
try {
randomNumber = await platform.invokeMethod('getRandomNumber');
} on PlatformException catch (e) {
randomNumber = 0;
}
setState(() {
_counter = randomNumber;
});
}
Note that we need to use the same platform channel identifier in the Flutter app and Android host app to get everything working properly. Look at the following complete source code of the modified demo application that displays random numbers:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GoMobileFlutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter with Go Mobile'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('example.com/gomobilelib');
int _counter = 0;
Future<void> _getRandomNumber() async {
int randomNumber;
try {
randomNumber = await platform.invokeMethod('getRandomNumber');
} on PlatformException catch (e) {
randomNumber = 0;
}
setState(() {
_counter = randomNumber;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'The following number was generated by Go:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _getRandomNumber,
tooltip: 'Get a random number',
child: const Icon(Icons.add),
),
);
}
}
If you run the Android application with the above source code using the flutter run
command, you can generate a new random number by clicking on the floating action button, as shown in the following preview.
Similar to the above example application, you can reuse your Go modules in your Flutter applications without rewriting them in Dart. Therefore, if you select Go for writing backend web services, you can productively use the core business logic modules directly with the Flutter frontend. Learn more about platforms channels from the official Flutter documentation.
This project’s source code is available at my GitHub repository.
Conclusion
In this article, we discussed the advantages of using Go for Flutter applications with a sample RESTful web service. Many developers use the RESTful web service pattern for modern cloud-oriented applications, but the RESTful approach doesn’t solve every technical requirement. For bidirectional asynchronous communications, for example, you may have to use the WebSockets protocol; for lightweight and high-speed streaming calls, you can consider using the gRPC protocol.
The Go package ecosystem provides packages for both WebSockets and gRPC protocols, so you can use Go to implement backends for your Flutter frontends with any preferred communication protocol.
As we discussed, Go backends bring many benefits for Flutter apps. There are many similarities in Go and Flutter development environments, and both technologies have the same popularity level and reputation, developer support, and growing package ecosystem.
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Top comments (0)