This article mainly introduces a set of Flutter Plugin components that can open wechat sharing, wechat login and wechat mini programs in my actual project development. Corresponding native functions have been realized on iOS and Android

Creation is not easy, I hope you give a start before leaving, 🙏🙏🙏🙏

Making the address

Public pub Warehouse address

Positive start

This paper is mainly divided into two parts, including the implementation of Flutter and the implementation of iOS (since I am mainly responsible for iOS development and the implementation of Android is implemented by my friend, I will not explain the android part).

Flutter part

This section is not too difficult, mainly using the MethodChannel to communicate with the native

static const MethodChannel _channel = const MethodChannel('flutter_wechate_share_plugin');
Copy the code

  • Register wechat development platform
  /// {' appID ': appID,'universalLink' : universalLink} register is a communication name that is agreed with the native endpoint about wechat registration, which is a parameter that the flutter endpoint passes to the native endpoint
  /// To register an application through wechat AppID, universalLink is a mandatory parameter for ios platform
  static Future<dynamic> register(String appid, {String universalLink = ' '}) async {
    if(appid.length == 0 || appid == null) return;
    var result = await _channel.invokeMethod(
        'register',
        {
          'appid': appid,
          'universalLink' : universalLink
        }
    );
    return result;
  }
Copy the code

  • sharing
/ * * the arguments mainly format: * {* "kind" : "text", "to" : * "title": "The title of message", * "description": "short description of message.", * "coverUrl": "https://example.com/path/to/cover/image.jpg", * "resourceUrl": "https://example.com/path/to/resource.mp4" * } * kind: Default: text -> text, image -> image, music -> music, video -> video, webpage -> webpage * to: The default sharing mode is Session -> Session list, Timeline -> Circle of Friends, favorite -> Favorites * Title: share title * Description: Share content description * coverUrl: Shared resource thumbnail * resourceUrl: Shared resource graph */
static Future<dynamic> share(Map<String.dynamic> arguments) async {
    arguments['kind'] = arguments['kind']????'text';
    arguments['to'] = arguments['to']????'session';
    var result = await _channel.invokeMethod(
        'share',
        arguments
    );
    return result;
  }

Copy the code

  • Open the wechat mini program
  /* * originalId: organization ID. This parameter is mandatory. * * path Path of the applet page
  static Future<dynamic> startMiniProgram(String originalId, {String path, String type = "0"}) async {
    if(originalId.length == 0 || originalId == null) {
      return;
    }
    var result = await _channel.invokeMethod(
        'startMiniProgram',
        {
          'originalId': originalId,
          'path': path,
          'type': type
        }
    );
    return result;
  }
Copy the code

  • Other functions
  /// Check whether wechat is installed locally
  static Future<dynamic> isWechatInstalled() async {
    var result = await _channel.invokeMethod(
        'isWechatInstalled'
    );
    return result == 'true' ? true : false;
  }

  /// Get the wechat version.
  static Future<dynamic> getApiVersion() async {
    var result = await _channel.invokeMethod(
        'getApiVersion'
    );
    return result;
  }

  /// Open the wechat app.
  static Future<dynamic> openWechat() async {
    var result = await _channel.invokeMethod(
        'openWechat'
    );
    return result;
  }
  
  /// landing
  /// {
  ///   "scope": "snsapi_userinfo",
  ///   "state": "customestate"
  /// }
  static Future<dynamic> login(Map<String.String> arguments) async {
    arguments['scope'] = arguments['scope']????'snsapi_userinfo';

    var result = await _channel.invokeMethod(
        'login',
        arguments
    );
    return result;
  }
Copy the code

The iOS section

  • First, let’s look at a core problem. The iOS end registers the callback about wechat

We know that in order to successfully invoke the wechat client, in addition to registering the wechat AppID, the native client also needs to call some methods of WXApi in several system proxies of the AppDelegate, such as:

#pragma mark - AppDelegate
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)application:(UIApplication *)application
              openURL:(NSURL *)url
    sourceApplication:(NSString *)sourceApplication
           annotation:(id)annotation {
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)application:(UIApplication *)application
    continueUserActivity:(NSUserActivity *)userActivity
      restorationHandler:(void (^) (NSArray *_Nonnull))restorationHandler {
    return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
Copy the code

But our Flugin project doesn't have an Appdelegate, so what do we do? Registered in the main works? It clearly not reality, the first code of invasive is too high, the second correction result is bad, here, because the framework provides us a FlutterApplicationLifeCycleDelegate this agent, implement the agent, We can also get a listening implementation of the delegate method in the Appdelegate in the Plugin project

  • Implementation plan 1: intercept the Appdelegate proxy method

1. Register as an agent

2. Implement proxy method, register the corresponding implementation method of WXApi

Do you have any problems implementing this approach? We know that the delegate model is one-to-one. If we implement the response to the Appdelegate in the Plugin, and if you later use this component in your own project, but the main project also wants to respond to the delegate method, there will be a conflict. Either the main project responds to the delegate method, and the Plugin will not respond. Either the plugin responds to the proxy method, and the main project doesn’t, so here’s the second implementation!

  • Solution two: Runtime + message forwarding for monitoring

  1. UIApplication+Hook class, to achieve the exchange of system methods
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method oldMethod = class_getInstanceMethod([self class].@selector(setDelegate:));
        Method newMethod = class_getInstanceMethod([self class].@selector(flutter_setDelegate:));
        methodExchange([self class], oldMethod, newMethod);
    });
}

static inline void
/// switch methods
/// @param class is a class that needs to swap methods
/// @param oldMethod method of the class
/// @param newMethod
methodExchange(Class class.Method oldMethod,Method newMethod)
{
    BOOL isAddedMethod = class_addMethod(class, method_getName(oldMethod), method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
    if (isAddedMethod) {
        class_replaceMethod(class, method_getName(newMethod), method_getImplementation(oldMethod), method_getTypeEncoding(oldMethod));
    } else{ method_exchangeImplementations(oldMethod, newMethod); }}static inline void
/// Get the two methods to swap
/// @param class The swapped class
/// @param selector implements the exchange class
classSelectorHook(Class class.SEL selector)
{
    Method oldMethod = class_getInstanceMethod(class, selector);
    NSString *oldSelName = NSStringFromSelector(method_getName(oldMethod));

    / / in FlutterWechateSharePlugin implementation
    Method newMethod = class_getInstanceMethod([FlutterWechateSharePlugin class].NSSelectorFromString([NSString stringWithFormat:@"flutter_%@",oldSelName]));

    methodExchange(class, oldMethod, newMethod);
}

- (void)flutter_setDelegate:(id<UIApplicationDelegate>) delegate
{
    static dispatch_once_t delegateOnceToken;
    dispatch_once(&delegateOnceToken, ^{
        // Exchange some methods of application
        classSelectorHook([delegate class].@selector(application:handleOpenURL:));
        classSelectorHook([delegate class].@selector(application:openURL:options:));
        classSelectorHook([delegate class].@selector(application:openURL:sourceApplication:annotation:));
        classSelectorHook([delegate class].@selector(application:continueUserActivity:restorationHandler:));
    });

    // flutter_setDelegate is the original delegate, ensuring that the original function is not affected
    [self flutter_setDelegate:delegate];
}

Copy the code
  1. In FlutterWechateSharePlugin class implements, handling of the hook method
#pragma mark - AppDelegate - Hookimplementation- (BOOL)flutter_application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    
    [FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application.url.nil];
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)flutter_application: (UIApplication *)application
              openURL: (NSURL *)url
    sourceApplication: (NSString *)sourceApplication
           annotation: (id)annotation {[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application.url.sourceApplication.annotation.nil];
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)flutter_application: (UIApplication *)application
            openURL: (NSURL *)url
            options: (NSDictionary<UIApplicationOpenURLOptionsKey.id> *)options {[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application.url.options.nil];
    return [WXApi handleOpenURL:url delegate:self];
}

- (BOOL)flutter_application: (UIApplication *)application
    continueUserActivity: (NSUserActivity *)userActivity
      restorationHandler: (void(^) (NSArray *_Nonnull))restorationHandler {[FlutterWechateSharePlugin performAppDelegateTarget:[self class] action:_cmd params:application.userActivity.restorationHandler.nil];
    return [WXApi handleOpenUniversalLink:userActivity delegate:self];
}
Copy the code

+ (BOOL) performAppDelegateTarget: (Class) CLS action: (SEL) SEL params: (id) arg,... The method mainly realizes the FlutterWechateSharePlugin forward in class

  • Implementation code of iOS and FLUTTER communication
This method is the core method for handling OC and FLUTTER communication
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    
    // Register for wechat
    if ([METHOD_REGISTERAPP isEqualToString:call.method]) {
        
        NSString *appId = call.arguments[@"appid"];
        NSString *universalLink = call.arguments[@"universalLink"];
        [WXApi registerApp:appId universalLink:universalLink];
        result(nil);
        
    }
    // Check whether wechat is installed locally
    else if ([METHOD_ISINSTALLED isEqualToString:call.method]) {
        
        result([NSNumber numberWithBool:[WXApi isWXAppInstalled]]);
    }
    // Open wechat
    else if ([METHOD_OPENWECHAT isEqualToString:call.method]) {
        
        result([NSNumber numberWithBool:[WXApi openWXApp]]);
    }
    // Wechat login
    else if ([METHOD_LOGINWECHAT isEqualToString:call.method]) {
        
        NSString* scope= call.arguments[@"scope"];
        NSString* state= call.arguments[@"state"];
        SendAuthReq *request = [[SendAuthReq alloc] init];
        request.scope = scope;
        request.state = state;
        [WXApi sendReq:request completion:^(BOOL success) {
            
        }];
    }
    / / share
    else if ([@"share" isEqualToString:call.method]) {
        
        NSDictionary *arguments = [call arguments];
        
        Webpage -> video -> video, webpage -> webpage
        NSString *kind = arguments[@"kind"];
        
        // Share text content
        if ([kind isEqualToString:METHOD_SHARETEXT{[])self handleShareTextCall:call result:result];
            
        }
        // Share image content
        else if ([kind isEqualToString:METHOD_SHAREIMAGE{[])self handleShareImageCall:call result:result];
        }
        // Share music
        else if ([kind isEqualToString:METHOD_SHAREMUSIC{[])self handleShareMusicCall:call result:result];
        }
        // Share web pages
        else if ([kind isEqualToString:METHOD_SHAREWEBPAGE{[])selfhandleShareWebPageCall:call result:result]; }}// Open the wechat applet
    else if ([METHOD_STARTMINIPROGRAM isEqualToString:call.method]) {
        
        [self handleStartMiniProgramCall:call result:result];
    }
    else {
    
        result(FlutterMethodNotImplemented); }}/// Share text
/// @param Call shared content
/// @param result
- (void)handleShareTextCall:(FlutterMethodCall *)call result:(FlutterResult)result {
    
    SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
    // To is the channel for sharing. Session -> Session list, Timeline -> Circle of Friends, favorite -> Favorites. The default is Session -> Session list
    NSString *to = call.arguments[@"to"];
    // Share it on moments
    if ([@"timeline" isEqualToString:to]) {
        req.scene = WXSceneTimeline;
    }
    // Add to the favorites list
    else if ([@"favorite" isEqualToString:to]) {
        req.scene = WXSceneFavorite;
    }
    // Share with friends
    else {
        req.scene = WXSceneSession;
    }
    
    req.bText = YES;
    req.text = call.arguments[@"text"];
    
    // Verify that the callback was shared successfully
    [WXApi sendReq:req completion:^(BOOL success){
                    
    }];
    result(nil);
}


/// Share images
/// @param Call shared content
/// @param result
- (void)handleShareImageCall:(FlutterMethodCall *)call result:(FlutterResult)result {
    
    SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
    // To is the channel for sharing. Session -> Session list, Timeline -> Circle of Friends, favorite -> Favorites. The default is Session -> Session list
    NSString *to = call.arguments[@"to"];
    // Share it on moments
    if ([@"timeline" isEqualToString:to]) {
        req.scene = WXSceneTimeline;
    }
    // Add to the favorites list
    else if ([@"favorite" isEqualToString:to]) {
        req.scene = WXSceneFavorite;
    }
    // Share with friends
    else {
        req.scene = WXSceneSession;
    }
    req.bText = NO;
    WXMediaMessage *message = [WXMediaMessage message];
    message.title = call.arguments[@"title"];
    message.description = call.arguments[@"description"];
    
    WXImageObject *mediaObject = [WXImageObject object];
    NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:call.arguments[@"resourceUrl"]]];
    if (imageData ! = nil) {
        mediaObject.imageData = imageData;
    } else {
        NSString *imageUri = call.arguments[@"url"];
        NSURL *imageUrl = [NSURL URLWithString:imageUri];
        mediaObject.imageData = [NSData dataWithContentsOfFile:imageUrl.path];
    }
    message.mediaObject = mediaObject;
    
    req.message = message;
    [WXApi sendReq:req completion:^(BOOL success){
        
    }];
    result(nil);
}


/// Share music
/// @param Call shared content
/// @param result
- (void)handleShareMusicCall:(FlutterMethodCall *)call result:(FlutterResult)result {
    
    SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
    // To is the channel for sharing. Session -> Session list, Timeline -> Circle of Friends, favorite -> Favorites. The default is Session -> Session list
    NSString *to = call.arguments[@"to"];
    // Share it on moments
    if ([@"timeline" isEqualToString:to]) {
        req.scene = WXSceneTimeline;
    }
    // Add to the favorites list
    else if ([@"favorite" isEqualToString:to]) {
        req.scene = WXSceneFavorite;
    }
    // Share with friends
    else {
        req.scene = WXSceneSession;
    }
    req.bText = NO;
    
    // Shared message session model
    WXMediaMessage *message = [WXMediaMessage message];
    message.title = call.arguments[@"title"];
    message.description = call.arguments[@"description"];
    
    // The resource path for music
    NSString *musicDataUrl = call.arguments[@"resourceUrl"];
    // The music page link address
    NSString *musicUrl = call.arguments[@"url"];
    // Music cover link address
    NSString *coverUrl = call.arguments[@"coverUrl"];
    
    // Data model for music
    WXMusicObject *mediaObject = [WXMusicObject object];
    mediaObject.musicUrl = musicUrl;
    mediaObject.musicDataUrl = musicDataUrl;
    
    NSData *coverImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:coverUrl]];
    [message setThumbImage:[UIImage imageWithData:coverImageData]];
    
    message.mediaObject = mediaObject;
    
    req.message = message;
    [WXApi sendReq:req completion:^(BOOL success){
        
    }];
    result(nil);
    
}


/// Share web pages
/// @param Call shared content
/// @param result
- (void)handleShareWebPageCall:(FlutterMethodCall *)call result:(FlutterResult)result {
    
    SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init];
    // To is the channel for sharing. Session -> Session list, Timeline -> Circle of Friends, favorite -> Favorites. The default is Session -> Session list
    NSString *to = call.arguments[@"to"];
    // Share it on moments
    if ([@"timeline" isEqualToString:to]) {
        req.scene = WXSceneTimeline;
    }
    // Add to the favorites list
    else if ([@"favorite" isEqualToString:to]) {
        req.scene = WXSceneFavorite;
    }
    // Share with friends
    else {
        req.scene = WXSceneSession;
    }
    req.bText = NO;
    // Shared message session model
    WXMediaMessage *message = [WXMediaMessage message];
    message.title = call.arguments[@"title"];
    message.description = call.arguments[@"description"];
    
    WXWebpageObject *mediaObject = [WXWebpageObject object];
    mediaObject.webpageUrl = call.arguments[@"url"];
    
    [message setThumbImage:[UIImage imageWithData:call.arguments[@"coverUrl"]]];
    message.mediaObject = mediaObject;
    
    req.message = message;
    [WXApi sendReq:req completion:^(BOOL success){
        
    }];
    result(nil);
    
}


/// open the wechat applet
/// @param call parameter content
/// @param result Returns the result
- (void)handleStartMiniProgramCall:(FlutterMethodCall *)call result:(FlutterResult)result {
    
    WXLaunchMiniProgramReq *req = [[WXLaunchMiniProgramReq alloc] init];
    req.userName = call.arguments[@"originalId"];
    req.path = call.arguments[@"path"];
    // The official version of the applet is opened by default
    req.miniProgramType = WXMiniProgramTypeRelease;
    NSString *miniProgramType = call.arguments[@"type"];
    if (miniProgramType.length ! = 0 && miniProgramType ! = nil) {
        
        NSInteger type = [call.arguments[@"type"] integerValue];
        switch (type) {
            case 0:
            {
                req.miniProgramType = WXMiniProgramTypeRelease;
            }
                break;
                
            case 1:
            {
                req.miniProgramType = WXMiniProgramTypeTest;
            }
                break;
                
            case 2:
            {
                req.miniProgramType = WXMiniProgramTypePreview;
            }
                break;
                
            default:
                break; }} [WXApi sendReq:req completion:^(BOOL success){
        
    }];
    result(nil);
}

#pragma mark - WXApiDelegate
- (void)onResp:(BaseResp*)resp {
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    [dictionary setValue:[NSNumber numberWithInt:resp.errCode] forKey:ARGUMENT_KEY_RESULT_ERRORCODE];
    if (resp.errStr ! = nil) {
        [dictionary setValue:resp.errStr forKey:ARGUMENT_KEY_RESULT_ERRORMSG];
    }
    if ([resp isKindOfClass:[SendMessageToWXResp class]]) {
        / / share
        [_channel invokeMethod:METHOD_ONSHAREMSGRESP arguments:dictionary];
    } else if ([resp isKindOfClass:[WXLaunchMiniProgramResp class]]) {
        // Open the applet
        if (resp.errCode = = WXSuccess) {
            WXLaunchMiniProgramResp *launchMiniProgramResp =
                (WXLaunchMiniProgramResp *)resp;
            [dictionary setValue:launchMiniProgramResp.extMsg
                          forKey:ARGUMENT_KEY_RESULT_EXTMSG];
        }
        [_channel invokeMethod:METHOD_ONLAUNCHMINIPROGRAMRESParguments:dictionary]; }}Copy the code

Thanks for reading!! Don’t be impatient! Study hard! Up every day!