Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Foreword: do not know why, have a kind of obsession to desktop application development, although have not developed what application come out all the time. Still prefer desktop applications over WEB applications. In my spare time, I have investigated many desktop application schemes. I was stupid before, and just recently I came up with the idea of using Flutter + gRPC. Of course, I haven’t thoroughly studied any disadvantages of flutter.

Technical solution of cross-platform desktop application

While WEB and mobile apps are popular today, desktop apps are in decline. But sometimes there’s a need to develop a little tool to do something simple. There are also many implementations of desktop applications.

Personal desktop applications need to be cross-platform (at least Win/Mac) and packaged small. So I did a search on the current technology.

  • WPF: it seems to be cross-platform, but I don’t want to use WPF because I have a bad impression of Microsoft.
  • Electron: The packing file is relatively large. If the function is small, the whole 100M feels a bit heavy.
  • Qt: available to C++ technologists and now available in various languages (Go, Rust, etc.). This requires in-depth study, and I don’t know C++ personally.
  • Python: Python also has a lot of UI frameworks, but I’m not very good at Python and I’m not used to interpreted languages.
  • Fyne: A UI framework for go. Ugly interface.
  • Go-astilectron: Go +Electron, there’s a review online that says there are some problems.
  • Flutter: The desktop application is not yet mature. It still feels like a mobile application. There is no official release of Flutter for the desktop. Some basic interaction components are missing.

The above schemes, Electron will not be considered, Qt feels a little difficult. I have learned go for a few days. I have used Flutter in the project, so I prefer Flutter + GO to be implemented.

As I mentioned earlier, the use of go-flutter for desktop applications was also a bit of a hassle.

For example, the Map keys used to interact with go-flutter must be of type interface{}. Transfer and receipt of a flutter must be converted, sometimes twice, or you may need to write your own advanced conversion methods.

Later, when I learned about gRPC, I saw official examples of GO and Dart. Then I thought of Flutter as the interface, GO for specific processing, and gRPC for communication. Have you fallen into Google’s trap? (I also thought about using Rust for specific processing, but I haven’t studied it in depth. GPRC official website also does not support Rust, only third-party library.)

As soon as I got to know them, those who knew them were already using them. See the article of relevant technology on the net not much also, want to write an introduction simply so.

gRPC

There are many articles about gRPC on the Internet, see the official website documents, some basic knowledge is almost understand. gRPC

Grpc-go /examples/ HelloWorld at master · gRPC /grpc-go (github.com)

Grpc-dart /example/ HelloWorld at Master · gRPC /grpc-dart (github.com)

In the examples above, the Server and Client sides of each language are respectively. Start the GO Server and dart Client, and you can theoretically communicate.

Run the Hello World example for the GO language

Configure the gRPC environment according to the official website. On the Server side and the Client side, run Go Mod Tidy to download dependencies. Then run the Server first, then run the Client, you can see the result.

The left side of the terminal is the Server command, and the right side is the Client command.

A Hello World example running the Dart language

Configure the gRPC environment according to the official website. After opening the project, you need to download the dependencies first. Open pubspec.yaml in VSCode and click Get Packages to download it.

After downloading, the code file no longer displays errors.

Run the Server first and then the Client.

Dart bin/server.dart command is run on the Server

Dart run the dart bin/client.dart command on the Client

The GO Server runs and the DART Client runs

Go language example helloworld. Proto: header comment omitted

syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
Copy the code

The dart language example helloWorld.proto: header is omitted

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
Copy the code

The proto files in the two examples above look the same except for the option part and should communicate directly. Try it first.

Start the Server side of the GO language:

Clients running the DART language:

Still too optimistic and wrong.

The error was:

gRPC Error (code: 12, codeName: UNIMPLEMENTED, message: grpc: Decompressor is not installed for grpc-encoding "gzip", details: [], rawResponse: null, trailers: {})
Copy the code

Error message:

grpc: Decompressor is not installed for grpc-encoding "gzip"
Copy the code

The unzip for grpc-encoding “gzip” is not installed.

The code of communication is analyzed:

The go language Server does not specify options.

s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
Copy the code

The DART language specifies the compression mode in the request options for Client requests.

final response = awaitstub.sayHello( HelloRequest().. name = name, options: CallOptions(compression:const GzipCodec()),
);
Copy the code

Remove the dart language Client request option.

final response = awaitstub.sayHello( HelloRequest().. name = name,// options: CallOptions(compression: const GzipCodec()),
);
Copy the code

Run again and you can communicate successfully.

The dart language Client request option has been removed for convenience. You can also add an option to the GO language Server. If the Server and Client have the same options, they can communicate correctly.

Develop desktop applications with Flutter + gRPC

Go and DART can now communicate with each other via gRPC. Flutter + gRPC is not far off.

Create the Flutter desktop application project

The latest version of Flutter should be configured by default to run desktop applications directly. If flutter does not run, see the following article for the configuration of flutter development desktop applications. Go – Flutter desktop application (1) First desktop application – Nuggets (juejin.cn)

Go ahead, build a Flutter project.

After the project is created, run flutter Run on the command line and you will be prompted to select the platform to run on.

MacBook-Pro:hellofluttergrpc xxx$ flutter run Multiple devices found: MacOS (Desktop) • macOS • Darwin x64 • Mac OS X 10.15.7 19H1419 Darwin X64 Chrome (Web) • Chrome • Web -javascript • Google Chrome 95.0.4638.54 [1]: macOS (macOS) [2]: Chrome (Chrome) Please choose one (To quit, press "Q /Q"):Copy the code

Select 1 to launch as a desktop application and see the familiar Flutter sample interface.

Add gRPC

  1. Add a GRPC dependency

    pubspec.yaml

    Async: ^2.2.0 GRPC: ^3.0.2 protobuf: ^2.0.0Copy the code

    You need to Get Packages to download dependencies.

  2. Copy the relevant DART file generated from the PROto file in gRPC’s DART language HelloWorld example above.

    In real development, you would write the PROTO file first and then generate the relevant DART code based on the PROTO file.

  3. In the main.dart file, add request handling.

    _MyHomePageState: This is a simple modification of the dart language HelloWorld example of client-side request handling.

    import 'package:grpc/grpc.dart';
    import 'src/generated/helloworld.pbgrpc.dart';
    Copy the code
    class _MyHomePageState extends State<MyHomePage> {
      String _message = ' ';
    
      void _callGRPC() async {
        final channel = ClientChannel(
          'localhost',
          port: 50051,
          options: ChannelOptions(
            credentials: ChannelCredentials.insecure(),
            codecRegistry:
                CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),
          ),
        );
        final stub = GreeterClient(channel);
    
        try {
          final response = awaitstub.sayHello( HelloRequest().. name ='flutter world'.// options: CallOptions(compression: const GzipCodec()),
          );
    
          setState(() {
            _message = response.message;
          });
    
          print('Greeter client received: ${response.message}');
        } catch (e) {
          print('Caught error: $e');
        }
        await channel.shutdown();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  _message,
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _callGRPC,
            tooltip: 'call gRPC',
            child: constIcon(Icons.add), ), ); }}Copy the code

    Try flutter Run. You need to start the Server side of the go language above

    Launching lib/main.dart on macOS in debug mode...
    lib/src/generated/helloworld.pbgrpc.dart:5:1: Error: A library can't opt out of null safety by default, when using sound null safety.
    / / @ dart = 2.7
    ^^^^^^^^^^^^^^
    lib/src/generated/helloworld.pb.dart:5:1: Error: A library can't opt out of null safety by default, when using sound null safety.
    / / @ dart = 2.7
    ^^^^^^^^^^^^^^
    
    Command PhaseScriptExecution failed with a nonzero exit code
    note: Using new build system
    note: Building targets in parallel
    note: Planning build
    note: Constructing build description
    ** BUILD FAILED **
    
    Building macOS application...                                           
    Exception: Build process failed
    Copy the code

    A bunch of empty security errors.

    The reason for this is that gRPC’s official DART example library is still old and has not yet added air safety. So dart files generated from proto files do not support null security.

    Forget about air safety.

    The best way to do this would be to use the latest gRPC library that supports DART air security to regenerate the dart file for communication from the PROto file. It is not confirmed that the latest library support does not support air safety.

    Flutter run –no-sound-null-safety You need to start the Server side of the go language above

    What? Another error.

    flutter: Caught error: gRPC Error (code: 14, codeName: UNAVAILABLE, message: Error connecting: SocketException: Connection failed (OS Error: Operation not permitted, errno = 1), address = localhost, port = 50051, details: null, rawResponse: null, trailers: {})
    Copy the code

    In fact, the reason for the error is very simple, is not authorized. But because of my inexperience, this problem has bothered me for a long time. Then a fellow member of the Flutter group told me that I needed to add permissions.

    The content of this article is as follows: Flutter gRPC development desktop application code: 14 / codeName: UNAVAILABLE/Operation Not permitted – nuggets

    At the macos/Runner DebugProfile. Entitlements and the entitlements and permissions.

    <key>com.apple.security.network.client</key>
    <true/>
    Copy the code

    DebugProfile.entitlements:

    
            
    <! DOCTYPEplist PUBLIC "- / / / / DTD PLIST Apple 1.0 / / EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
            <key>com.apple.security.app-sandbox</key>
            <true/>
            <key>com.apple.security.cs.allow-jit</key>
            <true/>
            <key>com.apple.security.network.server</key>
            <true/>
            <key>com.apple.security.network.client</key>
        <true/>
    </dict>
    </plist>
    Copy the code

    Release.entitlements:

    
            
    <! DOCTYPEplist PUBLIC "- / / / / DTD PLIST Apple 1.0 / / EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
            <key>com.apple.security.app-sandbox</key>
            <true/>
            <key>com.apple.security.network.client</key>
        <true/>
    </dict>
    </plist>
    Copy the code

    Flutter run –no-sound-null-safety should run again. You need to start the Server side of the go language above

    Click FAB in the lower right to display the message from the Go language Server. Done!!

supplement

— No-sound-null-safety should also be added to the package of the Flutter engineering

For example, the package command in Mac:

flutter build macos --no-sound-null-safety
Copy the code

Afterword.

  1. Some of the above processing, is a compromise temporary solution, and is based on the official gRPC example to do. In formal development, it would look like this.

    • Let’s make it a proto file.
    • Generate server-side code and client-side code from the same proto file, respectively.
    • Write server-side processing and Client processing.
    • Then start the Server and run the Client.
  2. It was a very simple technical point, took some detours in the middle. There have been a lot of people to do so, just feel the online related articles are not much, their simple write, but also record the technical points.

  3. The file size of the flutter project is 38.4m. The size of the executable file packaged by the GO language Server is 11.1m. Add up to feel not small ~ tried a lonely? A go Server run time and a DART Client run time add up to a smaller run time than the Electron run time.