DEV Community

Cover image for From Zero to One: A Complete Guide to Implementing WeChat Sharing in HarmonyOS
刘年华
刘年华

Posted on

From Zero to One: A Complete Guide to Implementing WeChat Sharing in HarmonyOS

From Zero to One: A Complete Guide to Implementing WeChat Sharing in HarmonyOS

Introduction

Hello everyone, I'm Jun Moxiao from the Qinglan Code Organization.

As the HarmonyOS ecosystem continues to flourish, various WeChat functionalities can now be integrated. Today, I'll teach you how to implement all WeChat sharing features, primarily including WeChat mini-program sharing and WeChat H5 sharing. This guide will focus on the key code components, and you'll need to combine them into a utility class on your own.

First, Integrate the WeChat SDK

Simply enter ohpm i @tencent/wechat_open_sdk in the terminal to include the WeChat SDK dependency.

Create a HarmonyOS Application on the WeChat Platform (Skip if Already Created)

Using the WeChat SDK requires creating a HarmonyOS application on the WeChat development platform. You can check the link for the creation process, which won't be covered in this tutorial.

Preparation for WeChat Sharing Implementation

Add the following fields to module.json5:

Image description

Image description

Create a WX Instance (The Earlier the Better)

// WXApi is the openApi interface for communication between third-party apps and WeChat. Its instance is obtained through WXAPIFactory and requires the AppID that the application has applied for
export const WXApi = wxOpenSdk.WXAPIFactory.createWXAPI(APP_ID)
Enter fullscreen mode Exit fullscreen mode

Note:

  • The appID is the same as the Android application's appID on the WeChat platform. You can reuse it directly after creating a HarmonyOS application.

Now Let's Implement the WeChat Sharing Utility Class (Building the Foundation, Combine the Complete Utility Class on Your Own)

/**
 * WeChat Sharing
 */
export class WxShareViewModel {
  // Replace with your actual appID
  WXApi = wxOpenSdk.WXAPIFactory.createWXAPI('')
  static instance: WxShareViewModel | null = null
  isWXApp: boolean = this.WXApi.isWXAppInstalled()

  private constructor() {

  }

  static getInstance() {
    if (!WxShareViewModel.instance) {
      WxShareViewModel.instance = new WxShareViewModel()
    }
    return WxShareViewModel.instance
  }
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • I recommend implementing utility classes as singletons, which is more concise and prevents encapsulation from being contaminated.

Implement Text Sharing

/**
 * Share text
 * @param text Text content
 */
textShare(text: string) {
  this.isWXAPPCallback(() => {
    let textObject = new wxOpenSdk.WXTextObject
    textObject.text = text
    let mediaMessage = new wxOpenSdk.WXMediaMessage()
    mediaMessage.mediaObject = textObject
    let req = new wxOpenSdk.SendMessageToWXReq()
    req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
    req.message = mediaMessage
    this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
  })
}
Enter fullscreen mode Exit fullscreen mode

Implement H5 Sharing

1. It's important to note that when sharing H5 pages or mini-programs with images, you need to download the image buffer and compress it to below 64KB for sharing, otherwise it will result in errors with no response.

2. Let's first implement buffer compression to below 64KB

I highly recommend the compression algorithm from the Huawei official website

Note:

  • I won't demonstrate other compression methods here, only buffer format compression, since downloaded images are returned as buffers. We can directly compress the buffer to below 64KB and pass it to the SDK without further expansion.
  • I've modified Huawei's official example to make it directly usable for compressing buffer formats.
import { image } from '@kit.ImageKit';

/**
 * Image compression and saving
 * @param sourcePixelMap: Original PixelMap object of the image to be compressed
 * @param maxCompressedImageSize: Specified target size for image compression, in KB
 * @returns compressedImageInfo: Returns the final compressed image information
 */
export async function compressedImage(sourcePixelMap: image.PixelMap,
  maxCompressedImageSize: number): Promise<ArrayBuffer> {
  // Create ImagePacker object for image encoding
  const imagePackerApi = image.createImagePacker();
  const IMAGE_QUALITY = 0;
  const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };
  // Encode through PixelMap. compressedImageData is the image file stream obtained from packing.
  let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
  // Target compressed image byte length
  const maxCompressedImageByte = maxCompressedImageSize * 1024;
  // Image compression. First determine if the minimum byte size that can be compressed by packing with quality parameter set to 0 meets the specified image compression size. If it does, use packing to binary search for the quality closest to the specified image compression target size. If not, use scale to first resize the image, using a while loop to decrease the scale by 0.4 times each iteration, then use packing (with quality parameter set to 0) to get the compressed image size, ultimately finding the scaling factor closest to the specified image compression target size.
  if (maxCompressedImageByte > compressedImageData.byteLength) {
    // Use binary packing compression to get the image file stream
    compressedImageData =
      await packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);
  } else {
    // Use scale to first resize the image, using a while loop to decrease the scale by 0.4 times each iteration, then use packing (with quality parameter set to 0) to get the compressed image size
    let imageScale = 1;
    const REDUCE_SCALE = 0.4;
    // Check if the compressed image size is greater than the specified target size; if so, continue reducing the scale factor.
    while (compressedImageData.byteLength > maxCompressedImageByte) {
      if (imageScale > 0) {
        // Performance note: Since scale directly modifies the image PixelMap data, binary search for scale factor is not suitable. Here we use a loop to decrease by 0.4 times each iteration to determine the most suitable scale factor. If image compression quality requirements are not high, consider increasing the reduceScale value to reduce loops and improve scale compression performance.
        imageScale = imageScale - REDUCE_SCALE;
        await sourcePixelMap.scale(imageScale, imageScale);
        compressedImageData = await packing(sourcePixelMap, IMAGE_QUALITY);
      } else {
        // When imageScale is less than or equal to 0, it has no meaning, so end compression. We don't consider cases where the image scale factor is less than reduceScale.
        break;
      }
    }
  }
  return compressedImageData;
}


/**
 * Packing compression
 * @param sourcePixelMap: Original PixelMap of the image to be compressed
 * @param imageQuality: Image quality parameter
 * @returns data: Returns the compressed image data
 */
async function packing(sourcePixelMap: image.PixelMap, imageQuality: number): Promise<ArrayBuffer> {
  const imagePackerApi = image.createImagePacker();
  const packOpts: image.PackingOption = { format: "image/jpeg", quality: imageQuality };
  const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
  return data;
}

/**
 * Binary packing compression method
 * @param compressedImageData: ArrayBuffer of the compressed image
 * @param sourcePixelMap: Original PixelMap of the image to be compressed
 * @param imageQuality: Image quality parameter
 * @param maxCompressedImageByte: Target compressed image byte length
 * @returns compressedImageData: Returns the image data compressed by binary packing
 */
async function packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number,
  maxCompressedImageByte: number): Promise<ArrayBuffer> {
  // Image quality parameter range is 0-100, here we create an array for binary packing image quality parameters with 10 as the minimum binary unit.
  const packingArray: number[] = [];
  const DICHOTOMY_ACCURACY = 10;
  // Performance note: If image compression quality requirements are not high, consider increasing the minimum binary unit dichotomyAccuracy to reduce loops and improve packing compression performance.
  for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
    packingArray.push(i);
  }
  let left = 0;
  let right = packingArray.length - 1;
  // Binary compression of the image
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    imageQuality = packingArray[mid];
    // Perform packing compression based on the input image quality parameter, returning the compressed image file stream data.
    compressedImageData = await packing(sourcePixelMap, imageQuality);
    // Find a compression size as close as possible to but not exceeding the target
    if (compressedImageData.byteLength <= maxCompressedImageByte) {
      left = mid + 1;
      if (mid === packingArray.length - 1) {
        break;
      }
      // Get the image file stream data compressed with the next binary image quality parameter (mid+1)
      compressedImageData = await packing(sourcePixelMap, packingArray[mid + 1]);
      // Check if the image size compressed with the next image quality parameter (mid+1) is greater than the specified target size. If so, it means the current image quality parameter (mid) produces an image size closest to the target. Use the current mid parameter to get the final target compressed image data.
      if (compressedImageData.byteLength > maxCompressedImageByte) {
        compressedImageData = await packing(sourcePixelMap, packingArray[mid]);
        break;
      }
    } else {
      // The target value is not in the right half of the current range, move the right boundary of the search range to the left to narrow the search range and continue searching the left half in the next iteration.
      right = mid - 1;
    }
  }
  return compressedImageData;
}
Enter fullscreen mode Exit fullscreen mode

3. Implement Image Download

export function loadImageUrl(url: string, successCallBack: (ImgArrayBuffer: ArrayBuffer) => void) {
  http.createHttp()
    .request(url,
      {
        method: http.RequestMethod.GET,
      },
      async (error: BusinessError, data: http.HttpResponse) => {
        if (http.ResponseCode.OK === data.responseCode) {
          let imageBuffer: ArrayBuffer = data.result as ArrayBuffer;
          const imageSource = image.createImageSource(imageBuffer)
          imageSource.createPixelMap()
            .then((pixelMap: image.PixelMap) => {
              compressedImage(pixelMap, 64)
                .then((arrayBuffer) => {
                  successCallBack(arrayBuffer)
                })
            })
        }
      })
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • Download images using createHttp, with the url parameter being the actual network address
  • The second parameter, successCallBack, is the success callback function that processes the compressed arrayBuffer after a successful network request
  • Since Huawei's official example cannot directly compress arrayBuffer and requires pixelMap format, we need to convert arrayBuffer to pixelMap format

4. Implement WeChat Installation Check Callback Function

isWXAPPCallback(callback: Function) {
  if (!this.isWXApp) {
    AlertDialog.show({ message: JSON.stringify("Please install WeChat first") })
  } else {
    callback()
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Core Preparation Work is Complete, Now Let's Implement H5 Sharing

/**
 * Share H5
 * @param Url Web page address
 * @param title Title
 * @param description Description
 * @param imgUrl Image network address
 */
h5Share(Url: string, title: string, description: string, imgUrl: string) {
  this.isWXAPPCallback(async () => {
    const webpageObject = new wxOpenSdk.WXWebpageObject()
    webpageObject.webpageUrl = Url
    const mediaMessage = new wxOpenSdk.WXMediaMessage()
    mediaMessage.mediaObject = webpageObject
    mediaMessage.title = title
    mediaMessage.description = description
    loadImageUrl(imgUrl, async (buffer) => {
      mediaMessage.thumbData = new Uint8Array(buffer)
      const req = new wxOpenSdk.SendMessageToWXReq()
      req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
      req.message = mediaMessage
      this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • Wrap with the WeChat installation check callback function before executing the core code
  • Finally, use the image buffer download function, pass in the function after successful callback, and pass the compressed buffer to the SDK for sharing

Implement WeChat Mini-Program Sharing

/**
 * Share mini-program
 * @param userName Original ID of the mini-program (in gh_xxxx format)
 * @param path Path of the mini-program
 * @param miniprogramType Type of mini-program, default is release version
 * @param title Title
 * @param description Description
 * @param imgUrl Network image address
 */
miniProgramShare(userName: string, path: string, miniprogramType: number, title: string, description: string,
  imgUrl: string) {
  this.isWXAPPCallback(async () => {
    const miniProgramObject = new wxOpenSdk.WXMiniProgramObject()
    miniProgramObject.userName = userName
    miniProgramObject.path = path
    miniProgramObject.miniprogramType = miniprogramType
    const mediaMessage = new wxOpenSdk.WXMediaMessage()
    mediaMessage.mediaObject = miniProgramObject
    mediaMessage.title = title
    mediaMessage.description = description
    loadImageUrl(imgUrl, async (buffer) => {
      mediaMessage.thumbData = new Uint8Array(buffer)
      const req = new wxOpenSdk.SendMessageToWXReq()
      req.scene = wxOpenSdk.SendMessageToWXReq.WXSceneSession
      req.message = mediaMessage
      this.WXApi.sendReq(getContext(this) as common.UIAbilityContext, req)
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • The image compression function works the same as for H5 sharing
  • Most parameters are provided by the API

Top comments (0)