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, and friendly development environment.
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 compared to other popular backend languages.
In this article, we’ll discuss these advantages and verify them practically by building a full-stack application.
Application frontends usually communicate with the server-side modules with various network communication concepts, such as REST, WebSocket, GraphQL, 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.
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.
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. Go-based backends work well on low-end server computers. However, Flutter works well on low-end mobile devices.
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.
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.
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.
With shared Dart packages, we can easily reuse frontend and backend code if we use Dart to implement backend web services. Conduit, Shelf, and Angel help developers build RESTful APIs with Dart, but Dart’s server-side support is still growing and is not yet comparable with the Go ecosystem. So, you should think twice before using Dart for writing the backend.
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.
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 creating a separate Go web server instance.
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.
We are going to create a RESTful backend to produce a JSON-formatted product list. First, install the latest Go development tools from the official Go Downloads page or with a package manager tool (e.g., Snap) 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 added some JSON struct tags in the Product struct with the
json:
notation for converting exported title case struct fields to lowercase JSON fields. We use a hardcoded product list for demonstration purposes, but you can use any preferred database connection to fetch stored product details.
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.
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(Mapjson) { 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 RESTful endpoints you can use the repository pattern to organize the code better.
The above product service uses
localhost
in the URL, since I run the app on Chrome (Flutter web mode) for demonstration. If you need to test the app on a mobile device, use your computer’s local network IP address instead of
localhost
and use the same WiFi network for both PC and mobile.
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.
You will see the working product list application interface, as shown below.
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.
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. For example, on the Android platform, we can call the Java code via the Flutter platform channel APIs from Dart, then we can call the Go mobile-generated library functions via Java Go mobile bindings. 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:
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.
A particular fullstack application’s backend and frontend are two different and separate modules. An application’s frontend typically uses a technology stack-agnostic protocol over the network to communicate with the backend. Therefore, the choice of backend language doesn’t affect the frontend implementation directly.
But, as we discussed earlier, using Go as the backend language brings many non-technical and hidden technical benefits like code reusability. What if we use a backend technology other than Go for the Flutter app?
Let’s compare Go-powered backends with other popular Flutter backend options:
Comparison factor | Go | Node.js (JavaScript) | .NET Core (C#) | Dart | Java |
---|---|---|---|---|---|
Popular RESTful frameworks/libraries | Gin, Revel, Martini, Echo, Goji, and Gorilla | Express, Fastify, Nest.js, Koa, and Connect | Inbuilt .NET Core web API | Conduit, Shelf, Angel, Jaguar, and Aqueduct | Spring, Spark, Vert.x, and Jersey |
Non-RESTful communication strategies (i.e., WebSocket, GraphQL) | Has libraries for implementing WebSockets, GraphQL, and gRPC backends; offers an official module for WebSockets | Has libraries for implementing WebSockets, GraphQL, and gRPC backends | Has libraries for implementing GraphQL and gRPC backends; offers inbuilt support for WebSockets | Has libraries for implementing WebSockets, GraphQL, and gRPC backends; offers inbuilt support for WebSockets too | Has libraries for implementing GraphQL and gRPC backends; Java EE and community projects offer the WebSockets support |
Performance | Good multi-threaded performance; Go code gets compiled into optimized platform-specific Assembly with a minimal runtime | Suitable for real-time less CPU-extensive tasks due to the single-threaded nature; optimizes code execution with V8’s JIT | Good multi-threaded performance; ource code gets translated to optimized CIL (Common Intermediate Language) for the VM | Suitable for real-time less CPU-extensive tasks due to single-threaded nature; possible to generate native binaries for better performance | Good multi-threaded performance; source code gets translated to optimized bytecode for the VM |
Third-party open source packages availability | Good; every community package tends to follow Go’s minimal design concepts | Good; able to find many packages for literally anything, so it’s somewhat hard to find the overall best package for a specific need without a detailed comparison | Good, but most high-quality libraries come with proprietary licensing models | Growing developer community — Dart’s server-side ecosystem has not yet matured | Good; every community package tends to follow Java’s design principles and offer production-ready solutions |
Developer support and popularity | Rapid popularity growth since the first release in 2012; good developer support from maintainers and the community | Popular mostly because of its ease of development; good developer support from maintainers and the community | Popular mostly because of the usage in enterprise application development; comes with commercial components, but the community-based developer support is good | Growing developer support and popularity; some packages like Aqueduct have been discontinued by maintainers | Popular mostly because of the usage in enterprise application development; comes with commercial components, but the community-based developer support is good |
Similarities with Flutter | Similarities in the CLI, development workflows, language syntax, and internal design principles (i.e., the performance-first design); backed by the same organization: Google | Similarities in the language syntax and runtime | Similarities in the language syntax and design principles (solid OOP) | The same programming language is used, so code reusability is undboutedly great; backed by the same organization: Google | Similarities in the language syntax and design principles (solid OOP) |
Reusability of the backend code in Flutter | Able to auto-generate platform-specific language bindings with native libraries via Go mobile | Requires a rewrite | Requires a rewrite | Able to share the code directly with a Dart package | Able to share the code to Android with a Java package; requires a rewrite for iOS |
Many React developers typically like to build their backend services with Node.js, since they can work with JavaScript to develop the entire fullstack app. Similarly, it’s possible to develop a backend with Dart too. Unfortunately, Dart’s server-side ecosystem has still not matured, so you may face issues with community-based packages and developer support if you choose Dart for your app’s backend.
Node.js is also a popular backend choice, but many experienced developers don’t recommend Node.js for CPU-intensive operations. Node.js with TypeScript or Deno has some similarities with the Dart language syntax, but Node.js development workflows and experience have many differences (i.e., compilation, CLI features, etc.) with Flutter.
Go has many similarities with Dart, and Go’s development tools have similarities with Flutter development tools. Flutter and Go strive to achieve better performance and are backed by the same organization. Also, reusing your backend’s Go code in the Flutter app is possible. Therefore, using a Go-powered backend for your Flutter app is a good decision.
But, this doesn’t mean that other backend options will negatively affect your Flutter app. The choice of backend technology doesn’t impact the frontend. But, using Go in the backend for a Flutter app brings impressive advantages over other backend options.
Using a Backend-as-a-Service (BaaS) solution like Firebase or Supabase is also a popular modern backend option for Flutter apps. However, with this option, you still may have to create cloud functions with a preferred backend language to run custom code for performing actions that don’t belong to your frontend code.
In this article, we discussed the advantages of using Go for Flutter applications with a sample RESTful web service. We also compared Go with other popular backend options. 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, and the performance-first design principle.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.