Realm Database, Expo SDK 49 and Expo Router Getting Started
Tags: realm, expo, reactnative, mobile
Published: 2023-08-14 20:33:04

Steps for building a simple mobile application using Expo SDK, [Expo Router](https://docs.expo.dev/routing/introduction/), and [MongoDB Realm](https://realm.io/) in React Native and plain Javascript.

[Realm](https://realm.io/) is a fast, scalable alternative to SQLite with mobile to cloud data sync that makes building real-time, reactive mobile apps easy.

I did this tutorial for those who want to build in javascript and not typescript but also to show my approach to using Realm with Expo Router's file-based approach to routing.

This is a companion blog post to go along with this video.
{% youtube uH-X37WPGYw %}

## Getting Started

Create the application
```jsx
npx create-expo-app@latest --template blank@sdk-49 app-with-realm
```
change into project directory
```jsx
cd app-with-realm
```
Install additional libraries and packages for expo-router
```jsx
npx expo install expo-router@latest react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar react-native-gesture-handler
```

Make modifications to support expo-router in `package.json`

```jsx
{
  "main": "expo-router/entry"
}
```

Not using web so skipping that part of documentation, but add the scheme `app.json`

```jsx
"scheme": "app-with-realm",
```

update `babel.config.js` to include the new plugin.

```jsx
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['expo-router/babel'],
  };
};
```

Lets add the index page and then test it is working, remember the files all live in a new app directory that you have to create. `/app/index.js`

```jsx
// /app/index.js

import { Text } from 'react-native';

export default function Page() {
  return <Text>Home page</Text>;
}
```

####Installing Realm

```jsx
npm install realm @realm/react
```

to build locally run do the following, if not you will continue to get a pod install error

>Error: Missing Realm constructor. Did you run "pod install"? Please see https://realm.io/docs/react-native/latest/#missing-realm-constructor for troubleshooting*

```jsx
npx expo prebuild
```

then to run on ios, you will see the pod file get installed appropriately

```jsx
npm run ios
```

Now we need to create our `schema` will be using the one from the realm example but in plain javascript. Add file to `app` directory

```jsx
// app/Task.js

import Realm, { BSON } from "realm";

export class Task extends Realm.Object {
  _id
  description 
  isComplete 
  createdAt 

  static primaryKey = "_id";
  static schema = {
    name: "Task",
    primaryKey: "_id",
    properties: {
      _id: 'uuid',
      description: "string",
      createdAt: {
        type: "date",
        default: new Date(),
      },
      isComplete: {
        type: "bool",
        default: false,
        indexed: true,
      },
    },
  };

  constructor(realm, description) {
    console.log("in constructor");
    super(realm, {
      _id: new BSON.UUID(),
      description,
    });
  }
}
```

Now lets wrap our whole app with the provider by creating a layout at the app route. Add this file `_layout.js` to the root of you `app` directory.

We use the schema we created `Task` as a parameter to the `RealmProvider`

```jsx
// app/_layout.js

import 'react-native-get-random-values'
import { Stack} from "expo-router";
import { RealmProvider } from "@realm/react";
import { Task } from './Task';

export default function AppLayout() {

  return (
    <RealmProvider schema={[Task]}>
      <Stack />
    </RealmProvider>
  );
}
```

update `index.js` so that we can query our database using a `useQuery` hook provided by realm.

```jsx
// /app/index.js

import { Text, View } from "react-native";
import { useQuery } from "@realm/react";
import { Task } from "./Task";

export default function Page() {
  const tasks = useQuery(Task);
  console.log(tasks);
  return (
    <View>
      <Text>TASK LIST</Text>
      <Text>{JSON.stringify(tasks, null, 2)}</Text>
    </View>
  );
}
```

lets add some UI to add a Task

```jsx
// /app/index.js

import {
  Text,
  TextInput,
  View,
  StyleSheet,
  TouchableOpacity,
} from "react-native";
import { useQuery } from "@realm/react";
import { Task } from "./Task";
import { useRef } from "react";

export default function Page() {
  // ref to hold description
  const descriptionRef = useRef("");

  // get the tasks
  const tasks = useQuery(Task);

  return (
    <View style={{ height: Dimensions.get("screen").height - 132 }}>
      <Text style={styles.title}>TASK LIST</Text>
      {/* input for description */}
      <TextInput
        placeholder="Enter New Task"
        autoCapitalize="none"
        nativeID="description"
        multiline={true}
        numberOfLines={8}
        value={descriptionRef.current}
        onChangeText={(text) => {
          descriptionRef.current = text;
        }}
        style={styles.textInput}
      />
      {/*  button to save the new task */}
      <TouchableOpacity
        style={styles.button}
        onPress={() => {
          createNewTask();
        }}
      >
        <Text style={styles.buttonText}>SAVE TASK</Text>
      </TouchableOpacity>
      <Text>{JSON.stringify(tasks, null, 2)}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    margin: 16,
  },
  title: {
    fontSize: 18,
    margin: 16,
    fontWeight: "700",
  },
  label: {
    marginBottom: 8,
    fontSize: 18,
    fontWeight: "500",
    // color: "#455fff",
  },
  textInput: {
    fontSize: 20,
    borderWidth: 1,
    borderRadius: 4,
    // borderColor: "#455fff",
    paddingHorizontal: 8,
    paddingVertical: 4,
    marginBottom: 0,
    marginHorizontal: 16,
  },
  button: {
    backgroundColor: "grey",
    padding: 10,
    borderRadius: 5,
    marginTop: 8,
    marginLeft: 16,
    width: 120,
  },
  buttonText: {
    color: "white",
    textAlign: "center",
    fontWeight: "600",
    fontSize: 12,
  },
});
```

Now add the function, `createNewTask`

 to save the task to the realm database. We will us the `useRealm` hook in this function

```jsx
import { useRealm } from "@realm/react";
```

then inside the component

```jsx
const realm = useRealm();
```

Then in the component add the  code for the `createNewTask` function

```jsx
const createNewTask = () => {
  realm.write(() => {
     const newTask = new Task(realm, descriptionRef.current);

     // clear input field
		 descriptionRef.current = "";
   
     // return task
     return newTask;
  });
};
```

Run the code and add a task


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ppm67blzc0bivpcfb5g.png)

Lets add a component to render the tasks in a `FlatList`

```jsx
import { useRealm } from "@realm/react";
import { StyleSheet, View, Text, Dimensions, Pressable } from "react-native";
import { FlatList } from "react-native-gesture-handler";

export const TaskList = ({ data }) => {
  const realm = useRealm();

  const renderItem = ({ item }) => (
    <View style={styles.row}>
      <View style={styles.item}>
        <View style={{ display: "flex", flex: 12 }}>

          <Text style={{ fontSize: 22, fontWeight: 'bold', marginBottom:8 }}>{item.description}</Text>
          <Text style={{ fontSize: 18, marginBottom:4 }}>{item.createdAt.toString()}</Text>
          <Text style={{ }}>{item._id + ""}</Text>
        </View>
        <View style={{ display: "flex", alignSelf: "center" }}>
          <Pressable
            onPress={() => onToggleStatus(item)}
            style={[styles.status, item.isComplete && styles.completed]}
          >
            <Text style={[styles.icon]}>{item.isComplete ? "✓" : "○"}</Text>
          </Pressable>
        </View>
      </View>
      <Pressable onPress={()=>onDelete(item)} style={styles.deleteButton}>
        <Text style={styles.deleteText}>Delete</Text>
      </Pressable>
    </View>
  );

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item._id + ""}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    display: "flex",
    flexDirection: "row",
  },
  row: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: "#ccc",
    width: Dimensions.get("screen").width,
  },
  icon: {
    textAlign: "center",
    fontSize: 20,
    fontWeight: "bold",
    textAlignVertical: "center",
  },
  status: {
    width: 32,
    height: 32,
  },
  deleteButton: {
    backgroundColor: "red",
    margin: 8,
    marginLeft: 0,
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 8,
    width: 100,
  },
  deleteText: {
    textAlign: "center",
  },
});
```

Add Toggle function

```jsx
const onToggleStatus = (task) => {
  console.log(task);
  realm.write(() => {
    task.isComplete = !task.isComplete;
  });
};
```

Add delete Function

```jsx
const onDelete = (task) => {
  console.log(task);
  realm.write(() => {
    realm.delete(task);
  });
};

```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mnv5pb6fzsgzi9zp66j.png)

#### Links
- Realm Database - https://realm.io/ 
- Realm React-Native - https://www.mongodb.com/docs/realm/sdk/react-native/install/ 
- Expo Router Doc - https://docs.expo.dev/routing/introduction/ 

#### Social Media
- Twitter - https://twitter.com/aaronksaunders 
- Facebook - https://www.facebook.com/ClearlyInnovativeInc 
- Instagram - https://www.instagram.com/aaronksaunders/