{"id":456,"date":"2026-10-20T14:17:42","date_gmt":"2026-10-20T06:17:42","guid":{"rendered":"https:\/\/en.meishesdk.com\/blog\/?p=456"},"modified":"2026-10-20T14:17:43","modified_gmt":"2026-10-20T06:17:43","slug":"face-filters-sdk-for-ios-android-flutter-react-native","status":"publish","type":"post","link":"https:\/\/en.meishesdk.com\/blog\/456.html","title":{"rendered":"Face Filters SDK for iOS, Android, Flutter &amp; React Native"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Once a team standardises on a cross-platform framework, the integration question for a&nbsp;<strong>Face Filters SDK<\/strong>&nbsp;stops being &#8220;does it support our platform&#8221; and becomes &#8220;where does the render loop actually run.&#8221; Those are different questions, and teams that treat them as the same one end up rebuilding their capture screen six months later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Meishe Face Filters SDK is the filter and effects layer of the Face AR SDK family. It performs real-time rendering without pre-processing on the cross-platform audio\/video processing engine used across the product family, and that engine is reached differently depending on target: directly by native iOS and Android code, through a bridge layer by React Native, and through the short-video solution by Flutter teams.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article walks through those four paths, what each one actually touches, and the parts that no target gets.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Product Positioning<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Meishe Face Filters SDK evaluates one effect stack across capture, timeline editing, and live streaming. Individual elements and complete looks, with per-element intensity control, ship as authored packages rather than compiled behaviour, so the same content reaches every supported target.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The supported platform set is: iOS, Android, Web, Desktop (Windows), smart hardware, React Native, and Flutter (supported through the short-video solution, for cross-framework teams).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The positioning is deliberately bounded. The SDK does not ship a finished camera UI. Recording controls, permission handling, gallery access, and upload remain your product&#8217;s responsibility on every target \u2014 including the ones where the effects pipeline arrives pre-assembled.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724.png\" alt=\"Face Filters SDK for iOS, Android, Flutter &amp; React Native\" title=\"Face Filters SDK for iOS, Android, Flutter &amp; React Native\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"569\" src=\"https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724-1024x569.png\" alt=\"Face Filters SDK for iOS, Android, Flutter &amp; React Native\" alt=\"\" class=\"wp-image-457\" srcset=\"https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724-1024x569.png 1024w, https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724-300x167.png 300w, https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724-768x427.png 768w, https:\/\/en.meishesdk.com\/blog\/wp-content\/uploads\/2026\/10\/2026102006171724.png 1260w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2. Two integration shapes, four targets<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Underneath the four names there are really two shapes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Direct native modules.<\/strong>&nbsp;An iOS app links the iOS modules and drives them from Swift or Objective-C; an Android app does the same from Kotlin or Java. The camera feed, detection, compositing, and preview all stay in native code. If there is a cross-platform layer above, it dispatches commands and nothing else.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Cross-platform entry points.<\/strong>&nbsp;A React Native app reaches the same modules through a wrapper that exposes native views and methods to your JavaScript code. A Flutter team reaches them through the short-video solution, which carries the capture pipeline and effect evaluation together.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Both shapes consume the same asset packages and the same look descriptions, which is why one organisation can run native and cross-platform apps side by side without maintaining two content libraries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. iOS and Android: what the native path gives you<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\"><\/th><th class=\"has-text-align-left\" data-align=\"left\">iOS &amp; Android<\/th><th class=\"has-text-align-left\" data-align=\"left\">React Native<\/th><th class=\"has-text-align-left\" data-align=\"left\">Flutter<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-left\" data-align=\"left\">Engine entry point<\/td><td class=\"has-text-align-left\" data-align=\"left\">Native module<\/td><td class=\"has-text-align-left\" data-align=\"left\">Native module behind a bridge layer<\/td><td class=\"has-text-align-left\" data-align=\"left\">Short-video solution<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\">Preview ownership<\/td><td class=\"has-text-align-left\" data-align=\"left\">A native view you place in your layout<\/td><td class=\"has-text-align-left\" data-align=\"left\">A native view embedded in the React tree<\/td><td class=\"has-text-align-left\" data-align=\"left\">Preview from the short-video pipeline<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\">Effect asset package<\/td><td class=\"has-text-align-left\" data-align=\"left\">Shared<\/td><td class=\"has-text-align-left\" data-align=\"left\">Shared<\/td><td class=\"has-text-align-left\" data-align=\"left\">Shared<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\">Per-element intensity control<\/td><td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td><td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td><td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\">SDK-provided camera UI<\/td><td class=\"has-text-align-left\" data-align=\"left\">None<\/td><td class=\"has-text-align-left\" data-align=\"left\">None<\/td><td class=\"has-text-align-left\" data-align=\"left\">None<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The native path is the reference case, and its advantages are structural rather than cosmetic. Frames move from the camera straight into the engine&#8217;s own processing loop, so there is no intermediate copy you have to schedule. Detection and compositing stay off your UI thread. Describing an effect chain is a single declarative step, and adjusting one element&#8217;s intensity afterwards does not force the rest of the stack to re-evaluate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cost is honest too: you maintain two wrappers. Teams with both a native app and a cross-platform app usually write the native integration once, then let the React Native or Flutter wrapper call into it rather than re-deriving the logic.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. React Native: keep the render loop out of the JavaScript runtime<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The failure mode in React Native is specific and worth naming. Frames live in native memory. If you try to move them through JavaScript \u2014 passing pixels across the bridge, doing per-frame work in React, reconciling anything that changes every frame \u2014 you will spend your frame budget on marshalling rather than on effects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The split that holds up:&nbsp;<strong>your React layer issues declarative commands, and the native side owns the loop.<\/strong>&nbsp;A look becomes one method call. An intensity change becomes one method call. The preview is a native view embedded in your React tree, so React never reconciles it and never sees a frame.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">The snippet below is schematic. Package names, props, and method signatures are illustrative and are not the shipped API surface.<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Illustrative React Native integration sketch.\nimport { useRef } from \"react\";\nimport { FaceEffectView, useLookLibrary } from \"@meishe\/react-native-effects\"; \/\/ placeholder name\n\nexport function CaptureScreen({ lookId }: { lookId: string }) {\n  const preview = useRef&lt;FaceEffectView&gt;(null);\n  const { looks, ready } = useLookLibrary({ channel: \"seasonal.autumn\" });\n\n  return (\n    &lt;FaceEffectView\n      ref={preview}\n      style={{ flex: 1 }}\n      captureMode=\"video\"\n      look={ready ? looks&#91;lookId] : null}\n      onReady={() =&gt; preview.current?.setElementIntensity(\"lip.satin.maple\", 0.55)}\n    \/&gt;\n  );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two things are worth noting about that shape. Nothing per-frame crosses the boundary, and the look identifier is resolved on the native side from the same packages your other apps use. Modern React Native architectures change how the bridge is implemented; they do not change the principle that frames belong below React.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Flutter: read the support scope before you plan<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Flutter appears on the platform list with a qualifier, and the qualifier is the plan:&nbsp;<strong>Flutter (supported through the short-video solution, for cross-framework teams)<\/strong>. You are not integrating a standalone effects-only package. You are adopting the short-video solution, which brings the capture pipeline, effect evaluation, and preview together in a form a cross-framework application can host.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Practically, that means your widget tree owns layout, navigation, product chrome, and state, while everything below the preview surface is owned by the pipeline you host. This tends to be easier than expected, because most of what teams want to build in Flutter \u2014 carousels, look lists, save-and-share flows \u2014 sits above the preview anyway.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">The snippet below is schematic. Class names and signatures are illustrative and are not the shipped API surface.<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Illustrative Flutter host sketch via the short-video entry point.\nfinal controller = MeisheShortVideoController(\n  captureMode: CaptureMode.video,\n  look: 'look.autumn.01',\n);\n\nawait controller.prepare();\nawait controller.assets.resolve(&#91;'look.autumn.01']);\n\n\/\/ Authoring lives in content. Runtime only chooses levels.\nawait controller.looks.setElementIntensity('lip.satin.maple', 0.55);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The same caution applies as on React Native: keep per-frame work out of Dart. Resolution and intensity changes are controller calls, and the rendering happens underneath.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. What all four share \u2014 and one thing none of them do<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Across native iOS, native Android, React Native, and Flutter through the short-video solution, three things are genuinely identical. The asset package format. The look structure, where looks reference elements instead of flattening them into images. And the update path, where new content reaches every target without recompiling anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now the part that no target gets. Motion and occlusion are where a rendered layer shows its seams: hair, hands, and accessories cross faces constantly, and the rendered layer follows the detected structure rather than any notion of object permanence. A hand sweeping past can briefly carry partially composited pixels before tracking re-establishes. There is no setting that removes this; there is only how you choose to frame it for end users, and how conservative your default looks are when the audience moves a lot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For anything makeup-adjacent, there is a second boundary. Rendered pigment lands inside the exposure and white balance of the frame it is composited onto, so the same shade reads differently in daylight, indoors, and under mixed sources. The render follows the picture; it does not correct the room. That is why per-element intensity works better as a small set of authored levels \u2014 light, standard, strong \u2014 than as a continuous slider pretending to be a measurement.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Do we need separate effect assets for each of the four targets?<\/strong><br>No. Assets are authored content rather than compiled behaviour, so one package set serves iOS, Android, React Native, and Flutter. What differs is the entry point your code uses to reach the engine, not the content that travels through it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Should frames ever cross into JavaScript or Dart?<\/strong><br>Only in narrow batch cases, such as exporting a still for thumbnail generation. Anything per-frame should stay below the UI runtime on every cross-platform target, because marshalling cost competes directly with render budget.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What exactly does the Flutter qualifier mean for us?<\/strong><br>It means Flutter support arrives through the short-video solution rather than as a standalone effects-only package. Plan around hosting that pipeline: your Flutter code owns product UI and state, the hosted pipeline owns capture, preview, and effect evaluation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Is there a ready-made camera interface included?<\/strong><br>No. The SDK ships no finished camera UI on any target. You build recording controls, permission prompts, gallery picking, and upload, which keeps the capture experience yours while the engine handles detection, tracking, and compositing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Can one shared effect library serve both a native and a cross-platform app?<\/strong><br>Yes, and this is the common arrangement. The identical packages resolve on every target; only the wrapper that calls into the engine differs, and it can be shared as internal source rather than maintained twice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ready to see it in your own stack?&nbsp;<a href=\"https:\/\/en.meishesdk.com\/register\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >Request a free trial<\/a>&nbsp;or&nbsp;<a href=\"https:\/\/en.meishesdk.com\/\" target=\"_blank\" rel=\"noreferrer noopener\" rel=\"nofollow\" >talk to our integration team<\/a>&nbsp;\u2014 we&#8217;ll help you scope the modules you actually need.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Once a team standardises on a cross-platform framework, the integration question for a&nbsp;Face Filters SDK&nbsp;stops being &#8220;does it support our platform&#8221; and becomes &#8220;where does the render loop actually run.&#8221; Those are different questions, and teams that treat them as the same one end up rebuilding their capture screen six months later. Meishe Face Filters [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":457,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[378,386,368,377,369],"class_list":["post-456","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-face-ar-sdk","tag-android","tag-face-filters-sdk","tag-flutter","tag-ios","tag-react-native"],"_links":{"self":[{"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/posts\/456","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/comments?post=456"}],"version-history":[{"count":1,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/posts\/456\/revisions"}],"predecessor-version":[{"id":458,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/posts\/456\/revisions\/458"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/media\/457"}],"wp:attachment":[{"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/media?parent=456"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/categories?post=456"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/en.meishesdk.com\/blog\/wp-json\/wp\/v2\/tags?post=456"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}