This file lists some common problems you may run into when developing Homescreens for IOS and Android
bridge.readFile will often fail on IOS. This seems to be caused by a quirk with the way the IOS bridge handles downloading files. Luckily, it will often work on repeated attempts, so you can create a dedicated readFile function that uses some kind of promise retry logic:
export const promiseWait = (ms: number) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
type RunAsyncWithRetryOptions = {
onFailure?: (e: any, tryNumber: number) => void;
retries?: number;
delay?: number;
};
export const runAsyncWithRetries = async <T>(
fn: () => Promise<T>,
options: RunAsyncWithRetryOptions = {},
startingRetries: number | undefined = undefined, // Should only be used internally
): Promise<T> => {
const finalOptions: Required<RunAsyncWithRetryOptions> = {
delay: 1000,
retries: 3,
onFailure: () => {},
...options,
};
const { retries, delay, onFailure } = finalOptions;
const actualStartingRetries = startingRetries ?? retries;
const currentAttemptNumber = actualStartingRetries - retries + 1;
try {
return await fn();
} catch (e) {
onFailure(e, currentAttemptNumber);
if (retries > 0) {
await promiseWait(delay);
return runAsyncWithRetries(fn, finalOptions, actualStartingRetries);
}
throw e;
}
};
/**
* `bridge.readFile` is risky on IOS, but often works when
* retried. All calls to `bridge.readFile` should instead
* use this function.
*/
export const readFile: typeof bridge.readFile = (params) =>
runAsyncWithRetries(() => bridge.readFile(params), { retries: 10 });There is a bug in the Hub where escaped characters are stringified incorrectly when bridge.readFile is called on IOS, which causes an error to be thrown when you try to parse those strings with JSON.parse. I have observed the issues occurring with line breaks and escaped quotation marks inside strings.Hopefully this bug will be fixed in the hub, but until then you need to carefully manage the contents of JSON files:
- Do not have any double quotes inside strings
- Minify JSON files before uploading them to remove line breaks
On mobile, when you fetch a list of files using bridge.getList, the downloadURL field will be missing, and at time of writing (10/05/2024) you cannot fetch it by using includeAttributes. you have 2 options:
- If the file is an image, you can use the
thumbnailfield instead. I assume but have not confirmed that this will yield a lower resolution, compressed version of the image, so if its essential that it be high resolution, see the other option - For non images, or images that need to high resolution, you will need to use
getEntityto fetch all the info of the file directly.
The text encoding logic on IOS can cause special characters to break (most common is '&'), you can pass any strings that come from entities to the decodeHtml function exports by @gs-libs/utils.
Alternatively, if the problem is proving to be very common, we can wrap the doCall method of the bridge with a function that deeply maps through all results and decodes any strings that contain HTML entities:
import { BridgeServices } from "@gs-libs/bridge";
import { decodeHtml } from "@gs-libs/utils";
export const deepMapObject = <T>(
obj: T,
mapFn: (value: any, key: string) => any,
): T => {
if (typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => deepMapObject(item, mapFn)) as any;
}
// obj is plain object...
return mapValues(obj, (value, key) => {
if (typeof value === "object") {
return deepMapObject(value, mapFn);
}
return mapFn(value, key);
}) as any;
};
/**
* Extend a function by providing an additional function which runs
* after the original, receiving the original function's return value.
* Uses `Proxy` to ensure any `this` references are preserved.
*/
export const extendFunction = <Fn extends (...args: any[]) => any, NewReturn>(
baseFn: (...args: Parameters<Fn>) => ReturnType<Fn>,
extension: (result: ReturnType<Fn>) => NewReturn,
) =>
new Proxy(baseFn, {
apply: (target, thisArg, args) => {
const result = Reflect.apply(target, thisArg, args);
return extension(result);
},
});
const decodeAllStringsInObject = (obj: any) =>
deepMapObject(obj, (value) => {
if (typeof value === "string") {
return decodeHtml(value);
}
return value;
});
export const bridge = new BridgeServices();
/**
* On IOS, text from entities returned from the bridge can have
* broken special characters, so we wrap the `doCall` method to
* fix any strings we get back.
*/
bridge.doCall = extendFunction(bridge.doCall, (resultPromise) =>
resultPromise.then(decodeAllStringsInObject),
);On IOS, stories don't include the excerpt field, so you should do something like this:
/**
* On IOS, stories don't include the `excerpt` field, so we
* need to fall back to the `message` field.
*
* NOTE: You also must specify `includeAttributes: ['message']`
* in the call
*/
export const getStoryExcerpt = (story: Story) => story.excerpt || story.message;IOS has showAlias field in the getList method turned off by default, but every other platform has it as on. Just manually specify showAlias: true in the getList call.
On most platforms, if you try to use getList to get the stories from a channel that the user does not have privilege to access, an error will be thrown. However, on IOS no error will be thrown and we instead just get an empty array. This is important if you are using getList to determine if a user has read access to a channel.
on iOS, any story updates first are stored as a "draft", which acts as a queue for pending updates. You can query this list with bridge.getDraftList(). To make sure you are always getting the latest version of the story, you can keep calling bridge.getDraftList() on an interval, and only proceed once the story you are looking for is no longer in the list.
Sometimes if an update fails, it will stay in the draft list for a while, but while have a field draftStatus: "failed", so you should not count an item as a draft if it has this field.
The mobile apps don't include a calendar menu like the web and windows apps do.
The easiest solution is to just instead open the 'Meetings' menu, which as far as I can tell just displays a user's scheduled calendar events in a list format.
Improving This In The Future: If clients keep asking for a proper calendar on mobile, we could develop one as a .btca app that we just upload for each client that wants it, then we just open that app from the homescreen. Best thing to do would be to setup the repo as a template so we can just drop in the client's branding and create a build.