flutter: SharedPreferences desktop plug-ins

flutter can build multi-terminal cross-platform applications, application development just need the desktop version, then try the legendary seamless migration.

However, just started in big trouble: the widespread use of mobile end SharedPreferences on the desktop side only macOS have achieved despite the introduction! shared_preferences: ^0.5.3+4No problems at compile time, but will throw in windows and linux platform runtime [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)exception.

This "seamless" came too fierce, bit by surprise ... waiting for the official version of the official categorically does not work, have to add their own in the implementation of the platform layer. Fortunately, the desktop can be a plug-and shared_preferencesdocking, combined on macOS implementation and sample programs provided to finally get out! to linux, for example, write about returning to the C ++.

Development environment:
Before you try to run the latest flutter1.9 integrated desktop application, but failed, so the development environment flutter1.8, it is OK to run up

flutterSDK: v1.8.0@stable

flutter Desktop: c183d46798b9642b8d908710de1e7d14a8573c86@master

pubspec.yaml:

dependencies:
  shared_preferences: ^0.5.3+4

Run the following command to ensure that it can run or reference this article (flutterSDK installed without further comment):

git clone https://github.com/google/flutter-desktop-embedding.git desktop
cd desktop/example
flutter run

Our example is based on the application SharedPreferences plugin developed.

Plug-in architecture

All plug-ins located in the root directory of desktop warehouse plugins, which flutter_pluginsespecially is a flutter at the other end (android / iOS / web) can also use plug-in, the others represent only use the desktop side (macOS / linux / windows) plug-in, to be achieved SharedPreferencesin the plugins/flutter_plugins/shared_preferences_fdenext, you can see the list of only macos.
so start a new plug-in on the linux platform:

  1. Create a directory and file

With alreadyurl_launcher_fde

mkdir -p plugins/flutter_plugins/shared_preferences_fde/linux && cd plugins/flutter_plugins/shared_preferences_fde/linux
cp ../../url_launcher_fde/linux/Makefile .
cp ../../url_launcher_fde/linux/url_launcher_fde_plugin.{cc,h} .
  1. Plug named

The Makefile is url_launcher_fde_pluginchanged shared_preferences_fde_plugin, it is required to compile the plug-in Makefile, just change it a name.
Local cpp file changed shared_preferences_fde_plugin.{cc,h}, but also changed the class name and the appropriate macro name, preferably with sedreplacement with search

FLUTTER_PLUGIN_EXPORT void SharedPreferencesRegisterWithRegistrar(
    FlutterDesktopPluginRegistrarRef registrar);

class SharedPreferencesPlugin : public flutter::Plugin {
  virtual ~SharedPreferencesPlugin();
private:
  SharedPreferencesPlugin();
}
...

RegisterWithRegistrarThe method in the name of a channel registered "plugins.flutter.io/shared_preferences"this name when an exception is thrown and is consistent.

void SharedPreferencesPlugin::RegisterWithRegistrar(
    flutter::PluginRegistrar *registrar) {
  auto channel = std::make_unique<flutter::MethodChannel<EncodableValue>>(
      registrar->messenger(), "plugins.flutter.io/shared_preferences",
      &flutter::StandardMethodCodec::GetInstance());
}

Also we need to say something special SharedPreferencesPlugin::HandleMethodCallthis method

void SharedPreferencesPlugin::HandleMethodCall(
    const flutter::MethodCall<EncodableValue> &method_call,
    std::unique_ptr<flutter::MethodResult<EncodableValue>> result) {
}

method_callMethod call is a structure that contains information layer dart pass over the name of the parameters to the type of reference passed; resultmethod result structure that contains the return value back to the required operating results and identify dart layer (identifies the call was successful ) to pass a pointer type.

Data type dart and c ++ two languages completely different, is how each pass? It uses a very important data structure flutter::EncodableValue, EncodableValuein c ++ layer of abstract data types dart layer, an instance can be used as bool, int, String , map, and list:

EncodableValue b(true); // 作为bool的EncodableValue
EncodableValue v(32); //作为int的EncodableValue
EncodableValue ret(EncodableValue::Type::kMap); //作为map的EncodableValue
EncodableMap& map = ret.MapValue(); // 操作起来必须先转成EncodableMap类型
std::string key = "some_key";
map[EncodableValue(key)] = v; // EncodableMap的K/V也必须是EncodableValue

flutter engine corresponding to finalize the final dart type.

Plug-dependent shared_preference the dart package, so it is necessary to see $FLUTTER_SDK/.pub-cache/hosted/$PUB_HOST/shared_preferences-0.5.3+4/lib/shared_preferences.dartwhat data transfer and needs.
The method is used to initialize the name 'getAll', need to return all the key-value pairs have been stored, you can implement an empty method to compile links:

void SharedPreferencesPlugin::HandleMethodCall(
    const flutter::MethodCall<EncodableValue> &method_call,
    std::unique_ptr<flutter::MethodResult<EncodableValue>> result) {
  const auto methodName = method_call.method_name();
  if (methodName.compare("getAll") == 0) {
    result->Error("no result", "but great~!");
  } else {
    result->NotImplemented();
  }
}

Associated plug-ins

Generation Plug

When building applications compiled in the plug-in also, so the need to transform the Makefile (Makefile application note is not plug-in), if the windows have to change sln file, a word that was on the association, after the transformation example/linux/Makefileis as follows:

# Executable name.
BINARY_NAME=flutter_desktop_example
# The C++ code for the embedder application.
SOURCES=flutter_embedder_example.cc

FLUTTER_PLUGIN_NAMES=shared_preferences_fde

# Default build type. For a release build, set BUILD=release.
# Currently this only sets NDEBUG, which is used to control the flags passed
# to the Flutter engine in the example shell, and not the complation settings
# (e.g., optimization level) of the C++ code.
BUILD=debug

# Configuration provided via flutter tool.
include flutter/generated_config

# Dependency locations
FLUTTER_APP_CACHE_DIR=flutter
FLUTTER_APP_DIR=$(CURDIR)/..
FLUTTER_APP_BUILD_DIR=$(FLUTTER_APP_DIR)/build
PLUGINS_DIR=$(CURDIR)/../../plugins
FLUTTER_PLUGINS_DIR=$(PLUGINS_DIR)/flutter_plugins

OUT_DIR=$(FLUTTER_APP_BUILD_DIR)/linux

# Libraries
FLUTTER_LIB_NAME=flutter_linux
FLUTTER_LIB=$(FLUTTER_APP_CACHE_DIR)/lib$(FLUTTER_LIB_NAME).so

PLUGIN_LIB_NAMES=$(foreach plugin,$(PLUGIN_NAMES) $(FLUTTER_PLUGIN_NAMES),$(plugin)_plugin)
PLUGIN_LIBS=$(foreach plugin,$(PLUGIN_LIB_NAMES),$(OUT_DIR)/lib$(plugin).so)
ALL_LIBS=$(FLUTTER_LIB) $(PLUGIN_LIBS)

# Tools
FLUTTER_BIN=$(FLUTTER_ROOT)/bin/flutter
LINUX_BUILD=$(FLUTTER_ROOT)/packages/flutter_tools/bin/linux_backend.sh

# Resources
ICU_DATA_NAME=icudtl.dat
ICU_DATA_SOURCE=$(FLUTTER_APP_CACHE_DIR)/$(ICU_DATA_NAME)
FLUTTER_ASSETS_NAME=flutter_assets
FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_BUILD_DIR)/$(FLUTTER_ASSETS_NAME)

# Bundle structure
BUNDLE_OUT_DIR=$(OUT_DIR)/$(BUILD)
BUNDLE_DATA_DIR=$(BUNDLE_OUT_DIR)/data
BUNDLE_LIB_DIR=$(BUNDLE_OUT_DIR)/lib

BIN_OUT=$(BUNDLE_OUT_DIR)/$(BINARY_NAME)
ICU_DATA_OUT=$(BUNDLE_DATA_DIR)/$(ICU_DATA_NAME)
FLUTTER_LIB_OUT=$(BUNDLE_LIB_DIR)/$(notdir $(FLUTTER_LIB))
ALL_LIBS_OUT=$(foreach lib,$(ALL_LIBS),$(BUNDLE_LIB_DIR)/$(notdir $(lib)))

# Add relevant code from the wrapper library, which is intended to be statically
# built into the client.
WRAPPER_ROOT=$(FLUTTER_APP_CACHE_DIR)/cpp_client_wrapper
WRAPPER_SOURCES= \
    $(WRAPPER_ROOT)/flutter_window_controller.cc \
    $(WRAPPER_ROOT)/plugin_registrar.cc \
    $(WRAPPER_ROOT)/engine_method_result.cc
SOURCES+=$(WRAPPER_SOURCES)

# Headers
WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
PLUGIN_INCLUDE_DIRS=$(OUT_DIR)/include
INCLUDE_DIRS=$(FLUTTER_APP_CACHE_DIR) $(WRAPPER_INCLUDE_DIR) $(PLUGIN_INCLUDE_DIRS)

# Build settings
CXX=clang++
CXXFLAGS.release=-DNDEBUG
CXXFLAGS=-std=c++14 -Wall -Werror $(CXXFLAGS.$(BUILD))
CPPFLAGS=$(patsubst %,-I%,$(INCLUDE_DIRS))
LDFLAGS=-L$(BUNDLE_LIB_DIR) \
    -l$(FLUTTER_LIB_NAME) \
    $(patsubst %,-l%,$(PLUGIN_LIB_NAMES)) \
    -ljsoncpp \
    -Wl,-rpath=\$$ORIGIN/lib

# Targets

.PHONY: all
all: $(BIN_OUT) bundle

# This is a phony target because the flutter tool cannot describe
# its inputs and outputs yet.
.PHONY: sync
sync: flutter/generated_config
    $(FLUTTER_ROOT)/packages/flutter_tools/bin/tool_backend.sh linux-x64 $(BUILD)

.PHONY: bundle
bundle: $(ICU_DATA_OUT) $(ALL_LIBS_OUT) bundleflutterassets

$(BIN_OUT): $(SOURCES) $(ALL_LIBS_OUT)
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(SOURCES) $(LDFLAGS) -o $@

$(WRAPPER_SOURCES) $(FLUTTER_LIB) $(ICU_DATA_SOURCE) $(FLUTTER_ASSETS_SOURCE): \
    | sync

$(OUT_DIR)/libshared_preferences_fde_plugin.so: | shared_preferences_fde

.PHONY: $(FLUTTER_PLUGIN_NAMES)
$(FLUTTER_PLUGIN_NAMES):
    make -C $(FLUTTER_PLUGINS_DIR)/$@/linux \
        OUT_DIR=$(OUT_DIR) FLUTTER_ROOT=$(FLUTTER_ROOT)

# Plugin library bundling pattern.
$(BUNDLE_LIB_DIR)/%: $(OUT_DIR)/%
    mkdir -p $(BUNDLE_LIB_DIR)
    cp $< $@

$(FLUTTER_LIB_OUT): $(FLUTTER_LIB)
    mkdir -p $(BUNDLE_LIB_DIR)
    cp $(FLUTTER_LIB) $(BUNDLE_LIB_DIR)

$(ICU_DATA_OUT): $(ICU_DATA_SOURCE)
    mkdir -p $(dir $(ICU_DATA_OUT))
    cp $(ICU_DATA_SOURCE) $(ICU_DATA_OUT)

# Fully re-copy the assets directory on each build to avoid having to keep a
# comprehensive list of all asset files here, which would be fragile to changes
# in the Flutter example (e.g., adding a new font to pubspec.yaml would require
# changes here).
.PHONY: bundleflutterassets
bundleflutterassets: $(FLUTTER_ASSETS_SOURCE)
    mkdir -p $(BUNDLE_DATA_DIR)
    rsync -rpu --delete $(FLUTTER_ASSETS_SOURCE) $(BUNDLE_DATA_DIR)

.PHONY: clean
clean:
    rm -rf $(OUT_DIR); \
    cd $(FLUTTER_APP_DIR); \
    $(FLUTTER_BIN) clean

diff easy to see what, in essence, is to build the application when adding a dependent ( ALL_LIBS_OUT), this is dependent on a number of .so files (according to our given these plugins directory .so file FLUTTER_PLUGINS_DIRplugin name) at ( FLUTTER_PLUGIN_NAMES) in the specified directory ( OUT_DIR) generate.

Load plugins

After generating the required load is completed, the load is static, that is, compile-time explicit call by the code directly on example/linux/flutter_embedder_example.ccthe diff file

index d87734f..bbc203d 100644
@@ -21,6 +21,8 @@
 
 #include <flutter/flutter_window_controller.h>
 
+#include <shared_preferences_fde_plugin.h>
+
 namespace {
 
 // Returns the path of the directory containing this executable, or an empty
@@ -65,6 +67,9 @@ int main(int argc, char **argv) {
     return EXIT_FAILURE;
   }
 
+  SharedPreferencesRegisterWithRegistrar(
+      flutter_controller.GetRegistrarForPlugin("SharedPreferences"));
+
   // Run until the window is closed.
   flutter_controller.RunEventLoop();
   return EXIT_SUCCESS;

So that you can run, although the results are still abnormal, but the error message is that we should write to die ' "no result", "but great ~!"' Show that this method successfully invoked ~
attention to compile this period of time is best to start the example / build directory is removed, this is the latest generation of intermediate files, otherwise the old generation due to possible cache files cause some weird problems abnormal exit runtime

Plug-in implementation

The final step is the nature of how we want to achieve in the platform layer SharedPreferenceskey / value storage capabilities. Because it already has shared_preferencesdart package, so to achieve its corresponding interface just fine.

name use
GetAll Returns all k / v initialization
commit Saving changes
clear Clear All k / v
remove Remove an item
setBool Deposit bool
setInt Int deposit

Search found around linux is not even widely used K / V store! Probably desktop applications are generally long data direct deposit file.

Later saw flutter-go project achieved SharedPreferencesusing a levelDB, so also is pleased with it, and found very easy to use! LevelDB of Key / Value byte array can be of any length, strong is strong, but not available here appropriate, take the time because of missing data type information, can not know the key corresponding to the value in the end it is int or bool, unless re-design the type of data stored in the storage format of the time. it's too much trouble.

In the bottom of android think shared_preference also a xml file and you need to know the type, but currently do not have too much to consider performance issues that directly json deposit is not finished yet?! So quickly find jsoncpp library, easy to use, directly manipulate files, ! after reading and data type information can be known, perfect
'getAll' as follows:

  if (methodName.compare("getAll") == 0) {
    std::ifstream infile;
    infile.open(kSavedFileName, std::ios::in);

    try {
      infile >> _root;
    } catch (std::exception& e) {
      _root = Json::objectValue;
    }
    infile.close();

    EncodableValue ret(EncodableValue::Type::kMap);
    EncodableMap& map = ret.MapValue();

    for (auto i = _root.begin(); i != _root.end(); i++) {
      Json::Value& obj = *i;
      const std::string key = i.name();
      map[EncodableValue(key)] = adaptJsonValue(obj);
    }

    result->Success(&ret);
  } else if (methodName.find("remove") == 0) {

adaptJsonValueThe method just type jsoncpp turn into the type of flutter corresponding
more Json::Valueusage see written before guide

static EncodableValue adaptJsonValue(const Json::Value& value) {
  switch (value.type()) {
    case Json::nullValue: {
      return EncodableValue(EncodableValue::Type::kNull);
    }
    case Json::booleanValue: {
      bool v = value.asBool();
      return EncodableValue(v);
    }
    case Json::uintValue:
    case Json::intValue: {
      int v = value.asInt();
      return EncodableValue(v);
    }
    case Json::realValue: {
      double v = value.asDouble();
      return EncodableValue(v);
    }
    case Json::arrayValue: {
      EncodableValue ev(EncodableValue::Type::kList);
      flutter::EncodableList& v = ev.ListValue();
      Json::Value def;
      for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
        v.push_back(adaptJsonValue(value.get(i, def)));
      }
      return ev;
    }
    case Json::objectValue: {
      return EncodableValue();
    }
    case Json::stringValue:
    default: {
      const char* v = value.asCString();
      return EncodableValue(v);
    }
  }
}

Code finally flutter project dart layer revalidation click on OK.

Guess you like

Origin www.cnblogs.com/lindeer/p/11619811.html