Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/jsActions/mobile-resources-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- We switched to a new sound library for the Play sound action to support react-native 0.84+.
- The Play sound action now plays audio files from online (network) documents on Android by downloading them to a version-based cache before playback.
- We have fixed the biometric authentication issue where it was not working on Android and crashing on iOS.
- Fixed an issue where the `TakePicture` and `TakePictureAdvanced` actions failed to capture photos on Android.

## [12.1.0] Native Mobile Resources - 2026-6-10

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,20 @@ export async function TakePicture(

function storeFile(imageObject: mendix.lib.MxObject, uri: string): Promise<boolean> {
return new Promise((resolve, reject) => {
fetch(uri)
.then(response => response.blob())
.then(blob => {
NativeModules.MxFileSystem.read(uri.replace("file://", ""))
.then((nativeBlob: unknown) => {
const blob = new Blob();
Object.assign(blob, { data: nativeBlob });
// eslint-disable-next-line no-useless-escape
const filename = /[^\/]*$/.exec(uri)![0];
const filePathWithoutFileScheme = uri.replace("file://", "");

// Set nativePayload so the patched FormData.prototype.append in NativeFileBackend
// replaces the blob value with { uri, name, type } for online uploads. The patch
// reads the third append() argument (fileName) and writes it onto nativePayload.name,
// which FormData.getParts() uses as the Content-Disposition filename.
(blob as any).nativePayload = { uri: `file://${uri}`, name: filename, type: "*/*" };

mx.data.saveDocument(
imageObject.getGuid(),
filename,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,20 @@ export async function TakePictureAdvanced(

function storeFile(imageObject: mendix.lib.MxObject, uri: string): Promise<boolean> {
return new Promise((resolve, reject) => {
fetch(uri)
.then(response => response.blob())
.then(blob => {
NativeModules.MxFileSystem.read(uri.replace("file://", ""))
.then((nativeBlob: unknown) => {
const blob = new Blob();
Object.assign(blob, { data: nativeBlob });
// eslint-disable-next-line no-useless-escape
const filename = /[^\/]*$/.exec(uri)![0];
const filePathWithoutFileScheme = uri.replace("file://", "");

// Set nativePayload so the patched FormData.prototype.append in NativeFileBackend
// replaces the blob value with { uri, name, type } for online uploads. The patch
// reads the third append() argument (fileName) and writes it onto nativePayload.name,
// which FormData.getParts() uses as the Content-Disposition filename.
(blob as any).nativePayload = { uri: `file://${uri}`, name: filename, type: "*/*" };

mx.data.saveDocument(
imageObject.getGuid(),
filename,
Expand Down
4 changes: 4 additions & 0 deletions packages/jsActions/nanoflow-actions-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

- Fixed an issue where base64-to-image decoding was failing.

## [7.2.0] Nanoflow Commons - 2026-7-3

- Close offline database connection before navigating between pages with OpenURL nanoflow action.

## [7.1.0] Nanoflow Commons - 2026-6-5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Other code you write will be lost the next time you deploy the project.
import { Base64 } from "js-base64";
import RNBlobUtil from "react-native-blob-util";
import { NativeModules } from "react-native";

// BEGIN EXTRA CODE
// END EXTRA CODE
Expand Down Expand Up @@ -45,21 +46,38 @@ export async function Base64DecodeToImage(base64: string, image: mendix.lib.MxOb
}

// Create a temporary file path
const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_image_${Date.now()}.png`;
const fileName = `image_${Date.now()}.png`;
const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/${fileName}`;

// Write Base64 data to a temporary file
await RNBlobUtil.fs.writeFile(tempPath, cleanBase64, "base64");

// Fetch the file as a blob
const res = await fetch(`file://${tempPath}`);
const blob = await res.blob();
// Read the file into the native blob store so offline mode works:
// NativeFileBackend.storeFile calls NativeFileSystem.save(blob.data, path)
// and blob.close() — a plain object has no .data getter or .close(), which
// crashes iOS via [NSInvocation invokeWithTarget:].
const nativeBlob = await NativeModules.MxFileSystem.read(tempPath.replace("file://", ""));
// Normalize: MxFileSystem.read may return 'length' instead of 'size'.
const blobData = { ...(nativeBlob as any) };
if (blobData.size === undefined && blobData.length !== undefined) {
blobData.size = blobData.length;
}
const blob = new Blob();
Object.assign(blob, { data: blobData });

// Set nativePayload so the patched FormData.prototype.append in NativeFileBackend
// replaces the blob value with { uri, name, type } for online uploads. The patch
// reads the third append() argument (fileName) and writes it onto nativePayload.name,
// which FormData.getParts() uses as the Content-Disposition filename.
(blob as any).nativePayload = { uri: `file://${tempPath}`, name: fileName, type: "image/png" };
const fileBlob = blob as Blob;

return new Promise((resolve, reject) => {
mx.data.saveDocument(
image.getGuid(),
"camera image",
fileName,
{},
blob,
fileBlob,
() => {
RNBlobUtil.fs.unlink(tempPath).catch(e => console.info("Temp file cleanup failed:", e));
resolve(true);
Expand Down
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/image-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We fixed an issue that caused images from entities to not render on Android in Online Synchronization mode

## [3.1.1] - 2026-6-10

### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/image-native/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "image-native",
"widgetName": "Image",
"version": "3.1.1",
"version": "3.1.2",
"description": "Display an image and enlarge it on click",
"copyright": "© Mendix Technology BV 2022. All rights reserved.",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FunctionComponent, Fragment, useCallback } from "react";
import { View } from "react-native";
import { ImageURISource, Platform, View } from "react-native";
import { SvgUri, SvgXml } from "react-native-svg";
import FastImageComponent, { Source } from "@d11/react-native-fast-image";
import { extractStyles } from "@mendix/pluggable-widgets-tools";
Expand Down Expand Up @@ -64,11 +64,19 @@ export const ImageIconSVG: FunctionComponent<ImageIconSVGProps> = props => {
);

if (image && (type === "staticImage" || type === "dynamicImage")) {
// FastImage's Glide/OkHttp client on Android can be initialized before the app wires up
// its cookie-decrypting network interceptor, causing remote (online document) images to
// fail to load with 401s. RN's own Image component always picks up the interceptor, so
// fall back to it for remote urls on Android.
const uri = typeof image === "object" ? (image as ImageURISource)?.uri : undefined;
const useFallback = Platform.OS === "android" && typeof uri === "string" && /^https?:\/\//i.test(uri);

return (
<FastImageComponent
testID={`${name}$Image`} // Broken because of https://github.com/DylanVann/react-native-fast-image/issues/221
source={image as Source | number}
resizeMode={resizeMode || "contain"}
fallback={useFallback}
style={[
initialDimensions?.aspectRatio ? { aspectRatio: +initialDimensions.aspectRatio?.toFixed(2) } : {},
width && height ? { width, height } : {},
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/image-native/src/package.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Image" version="3.1.1" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="Image" version="3.1.2" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Image.xml" />
</widgetFiles>
Expand Down
10 changes: 8 additions & 2 deletions packages/pluggableWidgets/image-native/src/utils/imageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ function getBundledAssetSource(value: NativeImage | Readonly<ImageURISource | st
return undefined;
}

function toAndroidUri(uri: string): string {
// Online images are served by the Mendix runtime as http(s) URLs and must be requested as
// such so cookies are attached; only local file paths need the file:/// scheme prepended.
return Platform.OS === "android" && !/^https?:\/\//i.test(uri) ? `file:///${uri}` : uri;
}

export async function convertImageProps(
datasource: DatasourceEnum,
imageIcon: DynamicValue<NativeIcon> | undefined,
Expand Down Expand Up @@ -74,14 +80,14 @@ export async function convertImageProps(
} else if (typeof imageValue === "object" && imageValue?.uri && imageValue?.name?.endsWith(".svg")) {
return {
type: "dynamicSVG", // Dynamic image SVG
image: (Platform.OS === "android" ? "file:///" : "") + imageValue.uri
image: toAndroidUri(imageValue.uri as string)
};
} else if (typeof imageValue === "object" && imageValue?.uri) {
return {
type: "dynamicImage", // Dynamic image
image: {
...imageValue,
uri: (Platform.OS === "android" ? "file:///" : "") + imageValue.uri
uri: toAndroidUri(imageValue.uri as string)
}
};
}
Expand Down
Loading