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
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
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