Nux
Nux

Reputation: 7098

embedding golang server with flutter

I have web server written in golang which uses graphql package gqlgen and gorm for database.

Since golang can be compiled and run on android I wanted to create offline version of my app where sqlite can be used for offline storage and import my whole server as an aar.

I have successfully built aar and add it on my flutter using gomobile by following instructions here

When I run my app, server is started on android it seems to work just fine and when opening http://localhost:8080/ on emulator's chrome app I am seeing graphql playground runs without any problem just like I see on browser in windows.

The only problem I face is that flutter app just shows a blank screen while server runs in background. The following are the logs printed when app is started

Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
√  Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:62561/DyGpOhyuekw=/ws
Syncing files to device sdk gphone64 x86 64...
I/GoLog   ( 6295): connect to http://localhost:8080/ for GraphQL playground
W/ux.offline( 6295): type=1400 audit(0.0:38): avc: denied { read } for name="somaxconn" dev="proc" ino=74990 scontext=u:r:untrusted_app:s0:c149,c256,c512,c768 tcontext=u:object_r:proc_net:s0 tclass=file permissive=0 app=com.nux.offline

I think maybe problem lies on the above logs avc: denied { read } for name="somaxconn" or something is causing the blocking of ui thread since its like flutter don't render a thing.

I am using flutter plugin to start server and this is ServerPlugin.kt

package com.mahesabu.server.server

import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import lib.Lib.startServer

/** ServerPlugin */
class ServerPlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "server")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "startServer") {
            try {
                val port = startServer() //from golang bindings
                result.success(port)
            } catch (e: Exception) {
                e.printStackTrace();
                result.error("Error in starting server", "${e.message}", null);
            }
        } else {
            result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

And this is is dart code

class Server {
  static const MethodChannel _channel = MethodChannel('server');

  static Future<String?> startServer() async {
    try {
      final String? port = await _channel.invokeMethod('startServer');
      return port;
    } catch (e) {
      log('startServer error: ${e.toString()}');
      return null;
    }
  }
}

and my app's main is as follows

import 'package:flutter/material.dart';
import 'package:server/server.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final port = await Server.startServer(); //problem

  print('port $port'); //This don't print

  runApp(const MyApp());
}

On go side this how i start server

//start.go
package util

import (
    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
    "log"
    "my-project/graph"
    "my-project/graph/generated"
    "net/http"
    "os"
)

const defaultPort = "8080"

// StartServer This way so that it can be invoked via libs
func StartServer(Offline bool) string {
    port := os.Getenv("PORT")
    if port == "" {
        port = defaultPort
    }

    db := InitDB(Offline)

    config := generated.Config{Resolvers: &graph.Resolver{
        DB:     db,
    }}

    srv := handler.NewDefaultServer(generated.NewExecutableSchema(config))

    http.Handle("/", playground.Handler("GraphQL playground", "/query"))
    http.Handle("/query", srv)

    log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))

    return port
}

and this is lib for generating bindings with gomobile

//lib.go
package lib

import "my-project/util"

// StartServer This way so that it can be invoked via libs
func StartServer() string {
    return util.StartServer(true)
}

Any help on fixing this will be appreciated.

Edit

I think problem occurs when embedded server tries to create a new port. I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like http://localhost:8080.

Now I think if there is a way to create port then I can run my app successfully but I don't know how exactly.

I was thinking if I can find any available port on android and use to start server maybe this stack could be possible. In kotlin something like this may work in finding port.

import java.net.ServerSocket

fun main() {
   val serverPort = ServerSocket(0)
   print(serverPort.toString())
}

but it crashes on android app when I try similar thing.

I have uploaded a repository on GitHub showing what I intend to do. It's just a simple golang server using gin and android app (with no flutter) it is available here.

Upvotes: 10

Views: 1410

Answers (1)

ch271828n
ch271828n

Reputation: 17623

"I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like localhost:8080"

To find out the root cause, try to run an HTTP server in android, such as How to create a HTTP server in Android?. If that succeeds, try to find the differences about how they deal with ports.

In addition, please be sure you have correct permission in androidmanifest.xml.

(Rephrased from my comments)

Upvotes: 2

Related Questions