Mobile Application Development Tutorial - React Native
React Native mobile option is available for Team or higher licenses. Therefore, if you don't have a commercial license, it's suggested to follow the article by downloading the source code of the sample application as described in the next chapter.
About This Tutorial
You must have an ABP Team or a higher license to be able to create a mobile application.
Download the Source Code
You can use the following link to download the source code of the application described in this article:
If you encounter the "filename too long" or "unzip" error on Windows, please see this guide.
The Book List Page
In react native there is no dynamic proxy generation, that's why we need to create the BookAPI proxy manually under the ./src/api
folder.
For the create menu item, navigate to ./src/navigators/DrawerNavigator.js
file and add BookStoreStack
to Drawer.Navigator
component.
Create the BookStoreStackNavigator
in ./src/navigators/BookStoreNavigator.js
, this navigator will be used for the BookStore menu item.
- BookStoreScreen will be used to store the
books
and authors
page
Add the BookStoreStack
to the screens object in the ./src/components/DrawerContent/DrawerContent.js
file. The DrawerContent component will be used to render the menu items.

Create Book List page
Before creating the book list page, we need to create the BookStoreScreen.js
file under the ./src/screens/BookStore
folder. This file will be used to store the books
and authors
page.
Create the BooksScreen.js
file under the ./src/screens/BookStore/Books
folder.
getBooks
function is used to fetch the books from the server.
i18n
API to localize the given key. It uses the incoming resource from the application-localization
endpoint.
DataList
component takes the fetchFn
property that we'll give to the API request function, it's used to fetch data and maintain the logic of lazy loading etc.

Creating a New Book
Add the CreateUpdateBook
Screen to the BookStoreNavigator
Like the BookStoreScreen
we need to add the CreateUpdateBookScreen
to the ./src/navigators/BookStoreNavigator.js
file.
To navigate to the CreateUpdateBookScreen
, we need to add the CreateUpdateBook
button to the BooksScreen.js
file.
After adding the CreateUpdateBook
button, we need to add the CreateUpdateBookScreen.js
file under the ./src/screens/BookStore/Books/CreateUpdateBook
folder.
- In this page we'll store logic, send post/put requests, get the selected book data and etc.
- This page will wrap the
CreateUpdateBookFrom
component and pass the submit function with other properties.
Create a CreateUpdateBookForm.js
file under the ./src/screens/BookStore/Books/CreateUpdateBook
folder and add the following code to it.
import React, { useRef, useState } from "react";
import {
Platform,
KeyboardAvoidingView,
StyleSheet,
View,
ScrollView,
} from "react-native";
import { useFormik } from "formik";
import i18n from "i18n-js";
import PropTypes from "prop-types";
import * as Yup from "yup";
import { useTheme, TextInput } from "react-native-paper";
import DateTimePicker from "@react-native-community/datetimepicker";
import { FormButtons } from "../../../../components/FormButtons";
import ValidationMessage from "../../../../components/ValidationMessage/ValidationMessage";
import AbpSelect from "../../../../components/Select/Select";
const validations = {
name: Yup.string().required("AbpValidation::ThisFieldIsRequired."),
price: Yup.number().required("AbpValidation::ThisFieldIsRequired."),
type: Yup.string().nullable().required("AbpValidation::ThisFieldIsRequired."),
publishDate: Yup.string()
.nullable()
.required("AbpValidation::ThisFieldIsRequired."),
};
const props = {
underlineStyle: { backgroundColor: "transparent" },
underlineColor: "#333333bf",
};
function CreateUpdateBookForm({ submit }) {
const theme = useTheme();
const [bookTypeVisible, setBookTypeVisible] = useState(false);
const [publishDateVisible, setPublishDateVisible] = useState(false);
const nameRef = useRef();
const priceRef = useRef();
const typeRef = useRef();
const publishDateRef = useRef();
const inputStyle = {
...styles.input,
backgroundColor: theme.colors.primaryContainer,
};
const bookTypes = new Array(8).fill(0).map((_, i) => ({
id: i + 1,
displayName: i18n.t(`BookStore::Enum:BookType.${i + 1}`),
}));
const onSubmit = (values) => {
if (!bookForm.isValid) {
return;
}
submit({ ...values });
};
const bookForm = useFormik({
enableReinitialize: true,
validateOnBlur: true,
validationSchema: Yup.object().shape({
...validations,
}),
initialValues: {
name: "",
price: "",
type: "",
publishDate: null,
},
onSubmit,
});
const isInvalidControl = (controlName = null) => {
if (!controlName) {
return;
}
return (
((!!bookForm.touched[controlName] && bookForm.submitCount > 0) ||
bookForm.submitCount > 0) &&
!!bookForm.errors[controlName]
);
};
const onChange = (event, selectedDate) => {
if (!selectedDate) {
return;
}
setPublishDateVisible(false);
if (event && event.type !== "dismissed") {
bookForm.setFieldValue("publishDate", selectedDate, true);
}
};
return (
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
<AbpSelect
key="typeSelect"
title={i18n.t("BookStore::Type")}
visible={bookTypeVisible}
items={bookTypes}
hasDefualtItem={true}
hideModalFn={() => setBookTypeVisible(false)}
selectedItem={bookForm.values.type}
setSelectedItem={(id) => {
bookForm.setFieldValue("type", id, true);
bookForm.setFieldValue(
"typeDisplayName",
bookTypes.find((f) => f.id === id)?.displayName || null,
false
);
}}
/>
{publishDateVisible && (
<DateTimePicker
testID="publishDatePicker"
value={bookForm.values.publishDate || new Date()}
mode={"date"}
is24Hour={true}
onChange={onChange}
/>
)}
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "margin"}
>
<ScrollView keyboardShouldPersistTaps="handled">
<View style={styles.input.container}>
<TextInput
mode="flat"
ref={nameRef}
error={isInvalidControl("name")}
onSubmitEditing={() => priceRef.current.focus()}
returnKeyType="next"
onChangeText={bookForm.handleChange("name")}
onBlur={bookForm.handleBlur("name")}
value={bookForm.values.name}
autoCapitalize="none"
label={i18n.t("BookStore::Name")}
style={inputStyle}
{...props}
/>
{isInvalidControl("name") && (
<ValidationMessage>{bookForm.errors.name}</ValidationMessage>
)}
</View>
<View style={styles.input.container}>
<TextInput
mode="flat"
ref={priceRef}
error={isInvalidControl("price")}
onSubmitEditing={() => typeRef.current.focus()}
returnKeyType="next"
onChangeText={bookForm.handleChange("price")}
onBlur={bookForm.handleBlur("price")}
value={bookForm.values.price}
autoCapitalize="none"
label={i18n.t("BookStore::Price")}
style={inputStyle}
{...props}
/>
{isInvalidControl("price") && (
<ValidationMessage>{bookForm.errors.price}</ValidationMessage>
)}
</View>
<View style={styles.input.container}>
<TextInput
ref={typeRef}
label={i18n.t("BookStore::Type")}
right={
<TextInput.Icon
onPress={() => setBookTypeVisible(true)}
icon="menu-down"
/>
}
style={inputStyle}
editable={false}
value={bookForm.values.typeDisplayName}
{...props}
/>
{isInvalidControl("type") && (
<ValidationMessage>{bookForm.errors.type}</ValidationMessage>
)}
</View>
<View style={styles.input.container}>
<TextInput
ref={publishDateRef}
label={i18n.t("BookStore::PublishDate")}
right={
<TextInput.Icon
onPress={() => setPublishDateVisible(true)}
icon="menu-down"
/>
}
style={inputStyle}
editable={false}
value={bookForm.values.publishDate?.toLocaleDateString()}
{...props}
/>
{isInvalidControl("publishDate") && (
<ValidationMessage>
{bookForm.errors.publishDate}
</ValidationMessage>
)}
</View>
<FormButtons style={styles.button} submit={bookForm.handleSubmit} />
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
input: {
container: {
margin: 8,
marginLeft: 16,
marginRight: 16,
},
borderRadius: 8,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
},
button: {
marginLeft: 16,
marginRight: 16,
},
});
CreateUpdateBookForm.propTypes = {
submit: PropTypes.func.isRequired,
};
export default CreateUpdateBookForm;
formik
will manage the form state, validation and value changes.
Yup
allows for the build validation schema.
AbpSelect
component is used to select the book type.
submit
method will pass the form values to the CreateUpdateBookScreen
component.


Update a Book
We need the navigation parameter for the get bookId and then navigate it again after the Create & Update operation. That's why we'll pass the navigation parameter to the BooksScreen
component.
Replace the code below in the BookScreen.js
file under the ./src/screens/BookStore/Books
folder.
Replace code below for CreateUpdateBookScreen.js
file under the ./src/screens/BookStore/Books/CreateUpdateBook/
get
method is used to fetch the book details from the server.
update
method is used to update the book on the server.
route
parameter will be used to get the bookId from the navigation.
Replace the CreateUpdateBookForm.js
file with the code below. We'll use this file for the create and update operations.
book
is a nullable property. It'll store the selected book, if the book parameter is null then we'll create a new book.


Delete a Book
Replace the code below in the BooksScreen.js
file under the ./src/screens/BookStore/Books
folder.
Delete
option is added to context menu list
removeOnClick
method will handle the delete process. It'll show an alert before the delete operation.


Authorization
Hide Books item in tab
Add grantedPolicies
to the policies variable from the appConfig
store
- In the
useEffect
function we'll check the currentUser
and policies
variables.
- useEffect's conditions will be the policies of the
BookStore
permission group.
Books
tab will be shown if the user has the BookStore.Books
permission

New Book
button is placed in the BooksScreen as a +
icon button. For the toggle visibility of the button, we need to add the policies
variable to the BooksScreen
component like the BookStoreScreen
component. Open the BooksScreen.js
file in the ./src/screens/BookStore/Books
folder and include the code below.
- Now the
+
icon button will be shown if the user has the BookStore.Books.Create
permission.

Hide the Edit and Delete Actions
Update your code as below in the ./src/screens/BookStore/Books/BooksScreen.js
file. We'll check the policies
variables for the Edit
and Delete
actions.

Author
Create API Proxy
The Author List Page
Add Authors Tab to BookStoreScreen
Open the ./src/screens/BookStore/BookStoreScreen.js
file and update it with the code below.
Create a AuthorsScreen.js
file under the ./src/screens/BookStore/Authors
folder and add the code below to it.
Create a CreateUpdateAuthorScreen.js
file under the ./src/screens/BookStore/Authors/CreateUpdateAuthor
folder and add the code below to it.
Create a CreateUpdateAuthorForm.js
file under the ./src/screens/BookStore/Authors/CreateUpdateAuthor
folder and add the code below to it.
import React, { useRef, useState } from "react";
import {
Platform,
KeyboardAvoidingView,
StyleSheet,
View,
ScrollView,
} from "react-native";
import { useFormik } from "formik";
import i18n from "i18n-js";
import PropTypes from "prop-types";
import * as Yup from "yup";
import { useTheme, TextInput } from "react-native-paper";
import DateTimePicker from "@react-native-community/datetimepicker";
import { FormButtons } from "../../../../components/FormButtons";
import ValidationMessage from "../../../../components/ValidationMessage/ValidationMessage";
const validations = {
name: Yup.string().required("AbpValidation::ThisFieldIsRequired."),
birthDate: Yup.string()
.nullable()
.required("AbpValidation::ThisFieldIsRequired."),
};
const props = {
underlineStyle: { backgroundColor: "transparent" },
underlineColor: "#333333bf",
};
function CreateUpdateAuthorForm({ submit, author = null }) {
const theme = useTheme();
const [birthDateVisible, setPublishDateVisible] = useState(false);
const nameRef = useRef();
const birthDateRef = useRef();
const shortBioRef = useRef();
const inputStyle = {
...styles.input,
backgroundColor: theme.colors.primaryContainer,
};
const onSubmit = (values) => {
if (!authorForm.isValid) {
return;
}
submit({ ...values });
};
const authorForm = useFormik({
enableReinitialize: true,
validateOnBlur: true,
validationSchema: Yup.object().shape({
...validations,
}),
initialValues: {
...author,
name: author?.name || "",
birthDate: (author?.birthDate && new Date(author?.birthDate)) || null,
shortBio: author?.shortBio || "",
},
onSubmit,
});
const isInvalidControl = (controlName = null) => {
if (!controlName) {
return;
}
return (
((!!authorForm.touched[controlName] && authorForm.submitCount > 0) ||
authorForm.submitCount > 0) &&
!!authorForm.errors[controlName]
);
};
const onChange = (event, selectedDate) => {
if (!selectedDate) {
return;
}
setPublishDateVisible(false);
if (event && event.type !== "dismissed") {
authorForm.setFieldValue("birthDate", selectedDate, true);
}
};
return (
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
{birthDateVisible && (
<DateTimePicker
testID="birthDatePicker"
value={authorForm.values.birthDate || new Date()}
mode={"date"}
is24Hour={true}
onChange={onChange}
/>
)}
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "margin"}
>
<ScrollView keyboardShouldPersistTaps="handled">
<View style={styles.input.container}>
<TextInput
mode="flat"
ref={nameRef}
error={isInvalidControl("name")}
onSubmitEditing={() => birthDateRef.current.focus()}
returnKeyType="next"
onChangeText={authorForm.handleChange("name")}
onBlur={authorForm.handleBlur("name")}
value={authorForm.values.name}
autoCapitalize="none"
label={i18n.t("BookStore::Name")}
style={inputStyle}
{...props}
/>
{isInvalidControl("name") && (
<ValidationMessage>{authorForm.errors.name}</ValidationMessage>
)}
</View>
<View style={styles.input.container}>
<TextInput
ref={birthDateRef}
label={i18n.t("BookStore::BirthDate")}
onSubmitEditing={() => shortBioRef.current.focus()}
right={
<TextInput.Icon
onPress={() => setPublishDateVisible(true)}
icon="menu-down"
/>
}
style={inputStyle}
editable={false}
value={authorForm.values.birthDate?.toLocaleDateString()}
{...props}
/>
{isInvalidControl("birthDate") && (
<ValidationMessage>
{authorForm.errors.birthDate}
</ValidationMessage>
)}
</View>
<View style={styles.input.container}>
<TextInput
mode="flat"
ref={shortBioRef}
error={isInvalidControl("shortBio")}
onSubmitEditing={() => authorForm.handleSubmit()}
returnKeyType="next"
onChangeText={authorForm.handleChange("shortBio")}
onBlur={authorForm.handleBlur("shortBio")}
value={authorForm.values.shortBio}
autoCapitalize="none"
label={i18n.t("BookStore::ShortBio")}
style={inputStyle}
{...props}
/>
</View>
<FormButtons style={styles.button} submit={authorForm.handleSubmit} />
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
const styles = StyleSheet.create({
input: {
container: {
margin: 8,
marginLeft: 16,
marginRight: 16,
},
borderRadius: 8,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
},
button: {
marginLeft: 16,
marginRight: 16,
},
});
CreateUpdateAuthorForm.propTypes = {
author: PropTypes.object,
submit: PropTypes.func.isRequired,
};
export default CreateUpdateAuthorForm;





Add Author
Relation To Book
Update BookAPI proxy file and include getAuthorLookup
method
Add AuthorName
to the Book List
Open BooksScreen.js
file under the ./src/screens/BookStore/Books
and update code below.
item.authorName
placed beside book type in the book list.
- We'll define
authors
prop in the CreateUpdateBookForm
component and it will be used for Authors dropdown.
- In the useEffect function we'll fetch authors from the server and set
authors
variable.
- Create authors dropdown input with
AbpSelect
component.
- Display selected author in the
TextInput



That's all. Just run the application and try to create or edit an author.