Mobile Development 19 min read

Developing a Flutter Plugin (native_image_view): Creation, Communication, Testing, and Publishing

The article walks through the complete workflow for building a Flutter plugin—using native_image_view as an example—including platform channel communication, Dart and native (iOS/Android) implementation, testing via an example app, and publishing to public or private Pub repositories.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Developing a Flutter Plugin (native_image_view): Creation, Communication, Testing, and Publishing

This article introduces the complete workflow for creating a Flutter plugin, using the native_image_view plugin as a concrete example. It covers the motivation for plugins, the Platform Channel mechanism, plugin creation commands, implementation on both Flutter and native sides (iOS and Android), testing, and publishing to public or private repositories.

1. Overview

Flutter’s ecosystem provides a Dart package repository, but many scenarios require custom plugins that wrap native functionality. The native_image_view plugin demonstrates how to expose image loading (including caching) from native code to Flutter.

2. Flutter‑Native Communication

Flutter communicates with native code via Platform Channels , which follow a client‑server model. Three channel types are available:

BasicMessageChannel – for sending strings or semi‑structured data.

MethodChannel – for invoking methods and receiving callbacks.

EventChannel – for streaming data.

All channels share three essential members:

String name – a unique identifier, e.g., com.tencent.game/native_image_view .

BinaryMessenger messager – the transport layer that passes binary data between Flutter and native.

MessageCodec/MethodCodec codec – handles encoding/decoding of data.

The native_image_view plugin uses only MethodChannel to request an image (local or remote) from native code and return the binary data.

3. Plugin Creation

Two kinds of Flutter components exist:

Flutter Package – contains only Dart code.

Flutter Plugin – contains Dart code plus iOS/Android native implementations.

Creating a Dart package:

flutter create --template=package hello

Creating a plugin with native code:

flutter create --template=plugin --org com.tencent.game -i objc -a java native_image_view

The generated structure includes android/ , ios/ , and example/ directories.

4. Plugin Development

4.1 Flutter side

Define a MethodChannel with the same name as the native side and invoke getImage :

class _NativeImageViewState extends State
{
  Uint8List _data;
  static const MethodChannel _channel = MethodChannel('com.tencent.game/native_image_view');
  @override
  void initState() {
    super.initState();
    loadImageData();
  }
  loadImageData() async {
    _data = await _channel.invokeMethod("getImage", {"url": widget.url});
    setState(() {});
  }
  @override
  Widget build(BuildContext context) {
    return _data == null
        ? Container(color: Colors.grey, width: widget.width, height: widget.height)
        : Image.memory(_data, width: widget.width, height: widget.height, fit: BoxFit.cover);
  }
}

4.2 iOS implementation

+ (void)registerWithRegistrar:(NSObject
*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"com.tencent.game/native_image_view" binaryMessenger:[registrar messenger]];
  NativeImageViewPlugin* instance = [[NativeImageViewPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"getImage" isEqualToString:call.method]) {
    [self getImageHandler:call result:result];
  } else {
    result(FlutterMethodNotImplemented);
  }
}

- (void)getImageHandler:(FlutterMethodCall*)call result:(FlutterResult)result {
  NSString *url = call.arguments[@"url"];
  if ([url hasPrefix:@"localImage://"]) {
    NSString *imageName = [url stringByReplacingOccurrencesOfString:@"localImage://" withString:@""];
    UIImage *image = [UIImage imageNamed:imageName];
    if (image) {
      NSData *imgData = UIImageJPEGRepresentation(image, 1.0);
      result(imgData);
    } else {
      result(nil);
    }
  } else {
    UIImage *image = [[SDImageCache sharedImageCache] imageFromCacheForKey:url];
    if (!image) {
      [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:[NSURL URLWithString:url]
        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
          if (finished) {
            result(data);
            [[SDImageCache sharedImageCache] storeImage:image forKey:url completion:nil];
          }
        }];
    } else {
      NSData *imgData = UIImageJPEGRepresentation(image, 1.0);
      result(imgData);
    }
  }
}

4.3 Android implementation

public class NativeImageViewPlugin implements FlutterPlugin, MethodCallHandler {
  private MethodChannel channel;
  private Context context;

  @Override
  public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "com.tencent.game/native_image_view");
    channel.setMethodCallHandler(this);
    context = flutterPluginBinding.getApplicationContext();
  }

  @Override
  public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("getImage")) {
      getImageHandler(call, result);
    } else {
      result.notImplemented();
    }
  }

  private void getImageHandler(MethodCall call, Result result) {
    String urlStr = (String) call.argument("url");
    Uri uri = Uri.parse(urlStr);
    if ("localImage".equals(uri.getScheme())) {
      // Load local drawable and return byte[]
    } else {
      Glide.with(context).download(urlStr).into(new CustomTarget
() {
        @Override
        public void onResourceReady(@NonNull File resource, @Nullable Transition
transition) {
          byte[] bytesArray = new byte[(int) resource.length()];
          try {
            FileInputStream fis = new FileInputStream(resource);
            fis.read(bytesArray);
            fis.close();
            result.success(bytesArray);
          } catch (IOException e) {
            result.error("READ_FAIL", e.toString(), null);
          }
        }
        @Override
        public void onLoadFailed(@Nullable Drawable errorDrawable) {
          result.error("LOAD_FAIL", "image download fail", null);
        }
        @Override
        public void onLoadCleared(@Nullable Drawable placeholder) {
          result.error("LOAD_CLEARED", "image load clear", null);
        }
      });
    }
  }
}

5. Plugin Testing

The generated example/ project references the plugin via a relative path, allowing developers to run and verify functionality before publishing.

native_image_view:
  path: ../

In main.dart the plugin is used as follows:

String url = "https://iknow-pic.cdn.bcebos.com/79f0f736afc379313a380ef3e7c4b74542a91193";
// String url = "localImage://wdqy.jpeg";
@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: Text('example')),
      body: Center(
        child: NativeImageView(url: url, width: 300, height: 200),
      ),
    ),
  );
}

6. Plugin Publishing

For public distribution, update pubspec.yaml with metadata and run:

flutter pub publish --dry-run

After fixing any warnings, publish with:

flutter pub publish

For private distribution, set up a private Pub server (e.g., using pub_server ) and add a publish_to field:

publish_to: http://192.168.1.3:8081

Then publish with:

flutter packages pub publish --server=http://192.168.1.3:8081

Consumers can reference the private plugin using the hosted syntax in their own pubspec.yaml .

Conclusion

The article walks through Platform Channel basics, the end‑to‑end creation, native implementation, testing, and both public and private publishing of a Flutter plugin. It demonstrates how encapsulating native capabilities in a plugin improves project modularity and reduces coupling between Flutter and platform code.

DartFlutteriosAndroidPlatform ChannelPlugin Developmentpublishing
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.