Preview PR Deployments for React Native Projects - Part 2
May 15, 2021
Note: This is a living document. I can/will alter/improve the content
Application side
We already covered the CI part of the implementation in this article. Please, check that out before you start to dive into this one.
- We'll run get request to App Center API to fetch the list of available deployments
- Add the list to your application
- Create a list view out of the list of available deployments
- Download the bundle when the user taps an item on the list
- Sync the bundle and codepush npm package will take care of the rest.
Appcenter API
App Center has a well-documented API which we consume from the application. A fetch request against codePushDeployments_list required for listing available deployments. The available endpoint for App Center is listed on https://openapi.appcenter.ms/.
Even though it is a well-documented API, it was a little bit struggle at first. After I found what I was looking for, it got way straightforward. Open API has an approachable UI that lets you run the curl command from a browser so the parameters are easy to access.
After getting my hands dirty with API, I went ahead and integrate the endpoint on the front end. The frontend code is something similar to this.
Frontend Code
I decided to build a dedicated developer screen that contains all available updates. I’m using react-router in my project but feel free to pick any navigation solutions of your choice. Here is my code for inspiration
import React, { useCallback, useMemo, useState } from 'react';
import { FlatList } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { Spinner } from './Spiner';
import { RootState } from '../../reducers';
import { ListHeader } from './ListHeader';
export const DeveloperMenu = ({
route,
}) => {
const dispatch = useDispatch();
const [refreshing, setRefreshing] = useState(false);
const { errorNotification } = useNotifications();
const deployments = useSelector(
(state) => state.codepush.data
);
const [onDownloadingText, setOnDownloadingText] = useState(
'Download is not initiated'
);
const isLoading = useSelector(
(state) => state.codepush.loading
);
React.useEffect(() => {
dispatch(codepush.listAvailableDeployments.trigger());
}, []);
const data = useMemo(
() =>
deployments.map(
({
name,
key,
latestRelease,
}: DeploymentProps): CodePushBucketProps & KeyExtractor => ({
key,
name,
deploymentKey: key,
targetBinaryRange: latestRelease
? latestRelease.targetBinaryRange
: 'Bucket create but not deployed',
})
),
[deployments]
);
const onError = (error: Error) => {
errorNotification(error.message);
};
const onDownloadProgress = (downloadProgress) => {
if (downloadProgress) {
setOnDownloadingText(
'Downloading ' +
downloadProgress.receivedBytes +
' of ' +
downloadProgress.totalBytes
);
}
};
const onRefresh = useCallback(async () => {
setRefreshing(true);
const promise = codepush.listAvailableDeploymentsPromise(
{},
dispatch
);
promise.then(
() => {
setRefreshing(false);
},
(error) => {
errorNotification(error.message);
setRefreshing(false);
}
);
}, []);
return (
<React.Fragment>
<FlatList
refreshing={refreshing}
onRefresh={onRefresh}
style={{
flex: 1,
}}
data={data}
renderItem={({ item })) => (
<ListItem
deploymentKey={item.deploymentKey}
name={item.name}
targetBinaryRange={item.targetBinaryRange}
codePush={codePush}
onDownloadProgress={onDownloadProgress}
onError={onError}
/>
)}
ListHeaderComponent={
<ListHeader onDownloadingText={onDownloadingText} />
}
/>
</React.Fragment>
);
};
The video will be inserted here.
Voila.
Oguzhan Murat Cakmak, starting his software career in Istanbul and advancing through roles in San Francisco at Uber and Hims&Hers, now does remote work from Kaş, Antalya, balancing his passion for technology with interests in kitesurfing, diving, hiking, and exploring diverse content on podcasts. Follow him on Twitter and Instagram