<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DETL INC</title>
    <description>The latest articles on DEV Community by DETL INC (@detl_inc).</description>
    <link>https://dev.to/detl_inc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2432995%2F7867c670-55c2-4381-9bc3-ee771d330981.png</url>
      <title>DEV Community: DETL INC</title>
      <link>https://dev.to/detl_inc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/detl_inc"/>
    <language>en</language>
    <item>
      <title>How to implement Image Upload with Expo Image Picker in React Native for a professional app launched app.</title>
      <dc:creator>DETL INC</dc:creator>
      <pubDate>Thu, 14 Nov 2024 16:38:38 +0000</pubDate>
      <link>https://dev.to/detl_inc/how-to-implement-image-upload-with-expo-image-picker-in-react-native-for-a-professional-app-1o13</link>
      <guid>https://dev.to/detl_inc/how-to-implement-image-upload-with-expo-image-picker-in-react-native-for-a-professional-app-1o13</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftctjxyeacgo6tvhjv79c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftctjxyeacgo6tvhjv79c.png" alt="Image description" width="720" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://www.detl.ca" rel="noopener noreferrer"&gt;DETL&lt;/a&gt;, we specialize in crafting stellar software applications that seamlessly blend hardcore engineering with sleek design. Our mission is to deliver both mobile and web applications that provide exceptional user experiences without compromising on aesthetics or functionality. In this blog post, we will guide you through how to implement an image picker in your Expo React Native application. This feature will allow users to upload a profile image, adding a personal touch to their profiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Implement a Profile Image Picker?
&lt;/h2&gt;

&lt;p&gt;Personalization is a key factor in enhancing user engagement within an app. Allowing users to upload a profile image not only personalizes their experience but also creates a deeper connection with the application. In this blog post, we’ll show you how to implement this feature using Expo’s &lt;code&gt;ImagePicker&lt;/code&gt;, enabling users to select and upload profile pictures seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Before embarking on this journey of explaining to you how to allow users to upload their profile image, I’d like to first break down the topics just so you’ll have a solid idea of what we will be covering in this blog post.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installing Required Packages&lt;/li&gt;
&lt;li&gt;Requesting Permissions&lt;/li&gt;
&lt;li&gt;Allowing Users to Pick an Image&lt;/li&gt;
&lt;li&gt;Uploading the Image to the Backend&lt;/li&gt;
&lt;li&gt;Updating the User’s Profile Picture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will explain each step in detail so you are not left confused :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Installing Required Packages
&lt;/h3&gt;

&lt;p&gt;Before we start, make sure you have Expo React Native application configured so that you can install the following package called &lt;code&gt;expo-image-picker&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expo install expo-image-picker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Importing Necessary Modules and Setting Up State
&lt;/h3&gt;

&lt;p&gt;In your profile component file, import the necessary package and as well as create the necessary states that will handle the uploading of the image.&lt;/p&gt;

&lt;p&gt;We have a folder called &lt;code&gt;profile -&amp;gt; index.tsx&lt;/code&gt; and we place the code which we will show you below inside of the &lt;code&gt;index.tsx&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useCallback, useEffect, useState } from 'react';
import {
  View,
  Text,
  StyleSheet,
  Image,
  TouchableOpacity,
  ScrollView,
  RefreshControl,
  Alert,
  ActivityIndicator,
} from 'react-native';
import * as ImagePicker from 'expo-image-picker';

// State variables
const [selectedImage, setSelectedImage] = useState('');
const [loadingImage, setLoadingImage] = useState(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have declared two states, the first state saves the selected image and the second state is a loading state which we will use when we are making an API call to our backend to save the image to the user's profile. We also import &lt;code&gt;expo-image-picker&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Requesting Permissions and Picking an Image
&lt;/h3&gt;

&lt;p&gt;We need to request permission to access the user’s media library and then allow them to select an image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const pickImage = async () =&amp;gt; {
  // Ask for permission to access the gallery
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
  if (status !== 'granted') {
    Alert.alert(
      'Permission Denied',
      'Permission to access gallery was denied'
    );
    return;
  }

  // Launch the image picker
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [1, 1],
    quality: 1,
    base64: true, // Include base64 in the result
  });

  if (!result.canceled) {
    setLoadingImage(true);
    const completeBase64String =
      'data:image/png;base64,' + result.assets[0].base64;
    uploadProfilePicture(completeBase64String, result.assets[0].uri);
  } else {
    setLoadingImage(false);
    Alert.alert('Error', 'Failed to select image');
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we request access from the user we check if the status was granted; if not, then we throw an alert. Once the status has been granted, the app will automatically navigate to the user's media library where their images are stored.&lt;/p&gt;

&lt;p&gt;Firstly, it is important to note that when we pick the image the user has selected, we make sure that we have access to the &lt;code&gt;base64&lt;/code&gt; format of the image since that is what we will be sending back to the backend when we make our API call.&lt;/p&gt;

&lt;p&gt;Secondly, as you can see from the code as shown below is that we attach a string to the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const completeBase64String =
      'data:image/png;base64,' + result.assets[0].base64;
    uploadProfilePicture(completeBase64String, result.assets[0].uri);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selected image returns a base64 without the first part of the image &lt;code&gt;data:image/png;base64,&lt;/code&gt; which is important, otherwise our backend will not be able to process the image correctly. What we do is attach a base64 image string to the result we get from the image picker so that our backend is able to process the image accordingly. This is a specific use case and can as well be handled by your backend but it is much easier in its implementation on the frontend.&lt;/p&gt;

&lt;p&gt;So far we have selected our image, received the equivalent base64 result of that image and now we will make a backend call with the base64 result of the image which is shown in the above code as such &lt;code&gt;uploadProfilePicture(completeBase64String, result.assets[0].uri)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Uploading the Image to the Backend
&lt;/h3&gt;

&lt;p&gt;We need to send the selected image to the backend server to update the user’s object. Whenever the user is re-authenticated, the image is then fetched from their profile and displayed on the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const uploadProfilePicture = async (base64Image, uri) =&amp;gt; {
  const endpoint = 'user';
  const method = 'PUT'; // Use PUT as per your requirement

  try {
    // Prepare the data object
    const data = {
      profilePicture: base64Image,
    };

    // Make the API request
    const response = await actionProvider.makeRequest(endpoint, method, data);

    if (response.status === 200) {
      Alert.alert('Success', 'Profile picture updated successfully');
      // Optionally, refresh user information
      fetchUserInformation(session);
      setLoadingImage(false);
      setSelectedImage(uri);
    } else {
      Alert.alert('Error', 'Failed to update profile picture');
      setLoadingImage(false);
    }
  } catch (error) {
    setLoadingImage(false);
    console.error('Error uploading profile picture:', error);
    Alert.alert(
      'Error',
      'An error occurred while uploading the profile picture'
    );
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In simple steps we will explain to you what is happening in the code above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Preparation&lt;/strong&gt;: We create a data object containing the base64 image which we will send as a payload within the API call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Call&lt;/strong&gt;: We make a PUT request to the backend API to update the user’s profile picture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Replace &lt;code&gt;actionProvider.makeRequest&lt;/code&gt; with your actual API endpoint method (e.g., using &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;axios&lt;/code&gt;). Since we have a hooks file where we make all of our API calls, we have placed our endpoint URL in this file so that we can import it wherever we need to make an API call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling Responses&lt;/strong&gt;: We check the response status and update the UI accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5: Displaying the Profile Image
&lt;/h3&gt;

&lt;p&gt;We update the UI to display the user’s profile image or a placeholder if none is set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;View style={styles.profileImageContainer}&amp;gt;
  &amp;lt;TouchableOpacity onPress={pickImage} activeOpacity={0.8}&amp;gt;
    {loadingImage ? (
      &amp;lt;View
        style={[
          styles.profileImage,
          {
            justifyContent: 'center',
            alignItems: 'center',
          },
        ]}
      &amp;gt;
        &amp;lt;ActivityIndicator color={'#4096C1'} /&amp;gt;
      &amp;lt;/View&amp;gt;
    ) : selectedImage ? (
      &amp;lt;&amp;gt;
        &amp;lt;Image
          source={{ uri: selectedImage }}
          style={styles.profileImage}
          resizeMode='cover'
        /&amp;gt;
      &amp;lt;/&amp;gt;
    ) : (
      &amp;lt;Image
        source={require('@/assets/images/profile/profileImage.png')}
        style={styles.profileImage}
        resizeMode='contain'
      /&amp;gt;
    )}
  &amp;lt;/TouchableOpacity&amp;gt;
&amp;lt;/View&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TouchableOpacity&lt;/strong&gt;: Makes the image clickable, allowing users to change their profile picture which runs the &lt;code&gt;pickImage&lt;/code&gt; function to show the media library.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loading Indicator&lt;/strong&gt;: Shows while the backend API call is made and the image is uploading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Selected Image&lt;/strong&gt;: Displays the user’s chosen profile picture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Placeholder Image&lt;/strong&gt;: Shown if no profile picture is set. This can be stored in a file inside your app like the &lt;code&gt;public → assets → images → profile → profileImage.png&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Entire Code
&lt;/h3&gt;

&lt;p&gt;Below we have posted the entire code. This way you can have a bird-eye view of the entire implementation and as well as follow the steps outlined above in a much more thorough way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import AnimatedWrapper from "@/components/shared/animation";
import GlobalButton from "@/components/shared/Button";
import { Colors, globalStyle } from "@/constants/Colors";
import { useSession } from "@/providers/Auth/AuthProvider";
import React, { useCallback, useEffect, useState } from "react";
import {
  View,
  Text,
  StyleSheet,
  Image,
  ImageBackground,
  TouchableOpacity,
  ScrollView,
  RefreshControl,
  Alert,
  ActivityIndicator,
} from "react-native";
import Subscription from "../../Subscription";

import * as ImagePicker from "expo-image-picker";
import actionProvider from "../../../../providers/actionProvider";
import { useToast } from "../../../../providers/useToast";

const HEIGHT = 80;

const index = () =&amp;gt; {
  const { user, session, fetchUserInformation } = useSession();

  const [modalVisible, setModalVisible] = useState(false);

  const [loading, setLoading] = React.useState(false);
  const [loadingImage, setLoadingImage] = useState(false);

  const [selectedImage, setSelectedImage] = useState("");

  const { showToast } = useToast();

  useEffect(() =&amp;gt; {
    // If user has a profile picture, set it
    if (user?.profilePicture) {
      setSelectedImage(user.profilePicture);
    }
  }, [user]);

  const onRefresh = useCallback(async () =&amp;gt; {
    // Start refreshing
    setLoading(true);
    fetchUserInformation(session);

    // Simulate a network request or any async action
    setTimeout(() =&amp;gt; {
      // Stop refreshing after 3 seconds
      setLoading(false);
    }, 3000);
  }, []);

  const pickImage = async () =&amp;gt; {
    // Ask for permission to access the gallery
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (status !== "granted") {
      Alert.alert(
        "Permission Denied",
        "Permission to access gallery was denied"
      );
      return;
    }

    // Launch the image picker
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [1, 1],
      quality: 1,
      base64: true, // Include base64 in the result
    });

    const completeBase64String =
      "data:image/png;base64," + result?.assets[0]?.base64;

    if (!result.cancelled) {
      setLoadingImage(true);
      uploadProfilePicture(completeBase64String, result?.assets[0].uri);
    } else {
      setLoadingImage(false);

      showToast("error", "Failed to update profile picture");
    }
  };

  const uploadProfilePicture = async (base64Image: string, uri: string) =&amp;gt; {
    const endpoint = "user";
    const method = "PUT"; // Use PUT as per your requirement

    try {
      // Prepare the data object
      const data = {
        profilePicture: base64Image,
      };

      // Make the API request
      const response = await actionProvider.makeRequest(endpoint, method, data);

      if (response.status === 200) {
        Alert.alert("Success", "Profile picture updated successfully");
        // Optionally, refresh user information
        fetchUserInformation(session);
        setLoadingImage(false);

        setSelectedImage(uri);
      } else {
        showToast("error", "Failed to update profile picture");
        setLoadingImage(false);
      }
    } catch (error) {
      setLoadingImage(false);

      console.error("Error uploading profile picture:", error);
      showToast(
        "error",
        "An error occurred while uploading the profile picture"
      );
    }
  };

  return (
    &amp;lt;View
      style={{
        flex: 1,
        backgroundColor: "white",
      }}
    &amp;gt;
      &amp;lt;ScrollView
        refreshControl={
          &amp;lt;RefreshControl
            refreshing={loading}
            onRefresh={onRefresh}
            colors={["#4096C1"]} // Customize spinner colors
            tintColor="#4096C1" // Customize spinner color for iOS
          /&amp;gt;
        }
        contentContainerStyle={{
          flex: 1,
        }}
      &amp;gt;
        &amp;lt;View style={styles.container}&amp;gt;
          &amp;lt;ImageBackground
            style={styles.innerContainer}
            source={require("@/assets/images/profile/profilePageImage.png")}
            imageStyle={styles.backgroundImage}
          &amp;gt;
            &amp;lt;View style={styles.profileImageContainer}&amp;gt;
              {/* This is the profile image section */}
              &amp;lt;TouchableOpacity onPress={pickImage} activeOpacity={0.8}&amp;gt;
                {loadingImage ? (
                  &amp;lt;View
                    style={[
                      styles.profileImage,
                      {
                        justifyContent: "center",
                        alignItems: "center",
                      },
                    ]}
                  &amp;gt;
                    &amp;lt;ActivityIndicator color={"#4096C1"} /&amp;gt;
                  &amp;lt;/View&amp;gt;
                ) : selectedImage ? (
                  &amp;lt;&amp;gt;
                    &amp;lt;Image
                      source={{ uri: selectedImage }}
                      style={styles.profileImage}
                      resizeMode="cover"
                    /&amp;gt;
                  &amp;lt;/&amp;gt;
                ) : (
                  &amp;lt;Image
                    source={require("@/assets/images/profile/profileImage.png")}
                    style={styles.profileImage}
                    resizeMode="contain"
                  /&amp;gt;
                )}
              &amp;lt;/TouchableOpacity&amp;gt;

              {/* This is the profile image section */}
            &amp;lt;/View&amp;gt;
          &amp;lt;/ImageBackground&amp;gt;
        &amp;lt;/View&amp;gt;
      &amp;lt;/ScrollView&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

export default index;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
  },
  innerContainer: {
    height: 200,
    backgroundColor: Colors.button.backgroundButtonDark,
    borderBottomLeftRadius: 40,
    borderBottomRightRadius: 40,
    alignItems: "center",
    justifyContent: "flex-end",
  },
  profileImageContainer: {
    position: "absolute",
    bottom: -HEIGHT / 2,
    height: HEIGHT,
    width: HEIGHT,
    borderRadius: HEIGHT / 2,
    backgroundColor: "white",
    padding: 5,
    justifyContent: "center",
    alignItems: "center",
  },
  backgroundImage: {
    borderBottomLeftRadius: 40,
    borderBottomRightRadius: 40,
  },
  profileImage: {
    height: HEIGHT / 1.2,
    width: HEIGHT / 1.2,
    borderRadius: HEIGHT / 1.5,
  },
  infoContainer: {
    alignItems: "center",
    marginTop: 30,
    flex: 1,
  },
  greetingText: {
    fontSize: 20,
    fontWeight: "bold",
    fontFamily: globalStyle.font.fontFamilyBold,
    textAlign: "center",
  },
  planContainer: {
    marginVertical: 10,
    backgroundColor: "rgba(64,150,193,.20)",
    borderRadius: 5,
    paddingVertical: 5,
    paddingHorizontal: 20,
  },
  planText: {
    color: "#000000",
    fontFamily: globalStyle.font.fontFamilyBold,
  },
  buttonContainer: {
    marginHorizontal: globalStyle.container.marginHorizontal,
    bottom: 10,
  },
  premiumContainer: {
    marginTop: 30,
    width: "90%",
    justifyContent: "space-between",
    alignItems: "center",
    backgroundColor: "#EFF6FA",
    flexDirection: "row",
    paddingVertical: 15,
    paddingHorizontal: 10,
    borderRadius: 10,
  },
  premiumContent: {
    flexDirection: "row",
    alignItems: "center",
    flex: 0.8,
  },
  premiumIcon: {
    height: 30,
    width: 30,
  },
  premiumText: {
    fontFamily: globalStyle.font.fontFamilyBold,
    fontSize: 16,
    marginLeft: 15,
  },
  upgradeText: {
    fontFamily: globalStyle.font.fontFamilyBold,
    color: Colors.button.textDark,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog post, we’ve demonstrated how to implement an image picker in your Expo React Native application to allow users to upload and update their profile images. This feature enhances user engagement by adding a personalized touch to their profiles.&lt;/p&gt;

&lt;p&gt;If there is anything unclear in this blog post, feel free to reach out to &lt;a href="https://www.detl.ca" rel="noopener noreferrer"&gt;DETL&lt;/a&gt; and we will be happy to help you through the journey of implementing this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Coding!&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to create authenticated routes with the new Expo SDK 51 using Expo Router</title>
      <dc:creator>DETL INC</dc:creator>
      <pubDate>Thu, 14 Nov 2024 15:36:54 +0000</pubDate>
      <link>https://dev.to/detl_inc/how-to-create-authenticated-routes-with-the-new-expo-sdk-51-using-expo-router-4mo1</link>
      <guid>https://dev.to/detl_inc/how-to-create-authenticated-routes-with-the-new-expo-sdk-51-using-expo-router-4mo1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lmlypmnbjoucshaexid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lmlypmnbjoucshaexid.png" alt="Image description" width="562" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this blog post, we will be covering “how to create authenticated routes” with the new Expo SDK 51. This guide is meant to be straightforward for our fellow engineers and it is recommended that you follow along with either an existing expo project or create a new project.&lt;/p&gt;

&lt;p&gt;We will skip the introduction of how to set up an expo project and jump straight to explaining what you need to do in order to create simple, yet powerful authenticated routes within your application. As a starter, make sure that your expo project version is &lt;code&gt;"expo": "~51.0.28"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you have the correct expo version, your folder should look like the image below. The image below is of an internal company project I am working on, so don’t be thrown off by all the other folders but if you were to look at your expo folder structure and the image below, you’ll notice some similarities such as the “app” folder and the “providers” folder. I am not sure if the “providers” folder is created upon creating a new expo project; if not, then please create a “providers” folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxfpfesbc1bzivpv837e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxfpfesbc1bzivpv837e.png" alt="Image description" width="466" height="1594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are mainly concerned with two folders in our expo project, the “app” folder and the “providers” → “Auth” folder. Ignore all the other folders in the above-posted image unless I specifically mention it in here. These two folders will allow us to create authenticated routes.&lt;/p&gt;

&lt;p&gt;Our “app” folder contains “index.tsx” and “_layout.tsx”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjgjq728dh63j3ic29sv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjgjq728dh63j3ic29sv.png" alt="Image description" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our “index.tsx” file looks like the image above. When users open up the app, the first thing I present to them is the “onboarding” component. You can change that to whatever you want your users to see first when they first open up the app and they are not logged in. Whether it is the “login” screen or the “sign up” screen, you make that decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusaamn92m9rcr6c4k3b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusaamn92m9rcr6c4k3b6.png" alt="Image description" width="720" height="1421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main file we are concerned with when creating authenticated routes is the _layout.tsx file. Make sure you copy and paste the contents of this file exactly as it is and I will tell you what you can change. The explanation of the useEffect hook which handles the authentication routing is below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;useEffect&lt;/code&gt; Hook on line 38&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;useEffect&lt;/code&gt; hook is used here to run the logic inside when any of the dependencies (&lt;code&gt;isAuthenticated&lt;/code&gt;, &lt;code&gt;segments&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt;, or &lt;code&gt;isConnected&lt;/code&gt;) change. It ensures that the appropriate routing and authentication logic is executed based on the current state of the user.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;const authState = segments[0] === "(auth)";&lt;/code&gt; on line 39&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The line &lt;code&gt;const authState = segments[0] === "(auth)";&lt;/code&gt; checks if the current route starts with &lt;code&gt;"(auth)"&lt;/code&gt;. This indicates that the user is in the authentication part of the app (which includes OnboardingFlow, help, etc.). This checks whether the user is currently trying to access the &lt;code&gt;"(auth)"&lt;/code&gt; part in the app.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqmnpml98zi39xxph817.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqmnpml98zi39xxph817.png" alt="Image description" width="462" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Handling Authentication&lt;/strong&gt;:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If the user is &lt;strong&gt;not authenticated&lt;/strong&gt; (&lt;code&gt;!isAuthenticated&lt;/code&gt;) and is currently trying to access an "auth" screen (like &lt;code&gt;OnboardingFlow&lt;/code&gt;), the user is redirected to the login page.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!isAuthenticated &amp;amp;&amp;amp; authState) {
  return router.replace("/login");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On line 48, if the user &lt;strong&gt;is authenticated&lt;/strong&gt; and &lt;strong&gt;not&lt;/strong&gt; in the “auth” group (meaning they’re in the main part of the app either login screen or signup screen), the code checks if onboarding is completed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user hasn’t completed onboarding (&lt;code&gt;onboardingCompleted&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;), they are redirected to the first onboarding screen &lt;code&gt;"/(auth)/OnboardingFlow/screenOne"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If onboarding is completed, they are redirected to the home screen (&lt;code&gt;"/(tabs)/home"&lt;/code&gt;).
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;else if (isAuthenticated == true &amp;amp;&amp;amp; !authState) {
  if (onboardingCompleted) {
    return router.replace("/(auth)/OnboardingFlow/screenOne");
  } else {
    return router.replace("/(tabs)/home");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that you can replace the following line to route to any authenticated pages you want&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.replace("/(auth)/OnboardingFlow/screenOne");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or you can simply just immediately route to the home page and avoid the “onboardingCompleted” check which I do in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Providers → Auth folder → AuthProviders.tsx&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here is an image of the &lt;code&gt;AuthProvider.tsx&lt;/code&gt; and &lt;code&gt;Auth folder&lt;/code&gt; which we will use to write our &lt;code&gt;sign up&lt;/code&gt; &lt;code&gt;sign in&lt;/code&gt; and &lt;code&gt;sign out&lt;/code&gt; function. In the above &lt;code&gt;_layout.tsx&lt;/code&gt; image you can see that we are wrapping our app with &lt;code&gt;SessionProvider&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpk1rmg8k0shgf98ove4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpk1rmg8k0shgf98ove4.png" alt="Image description" width="550" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcfiliczovpkhwqyk9lx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcfiliczovpkhwqyk9lx.png" alt="Image description" width="550" height="1964"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AuthContext and Context Provider Setup
&lt;/h3&gt;

&lt;p&gt;Context is used to share the authentication state (logged in, logged out, onboarding, etc.) across different components in your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const AuthContext = createContext&amp;lt;AuthContextType&amp;gt;({
  signIn: async () =&amp;gt; {},
  signOut: () =&amp;gt; {},
  signUp: async () =&amp;gt; {},
  session: null,
  isLoading: false,
  isAuthenticated: false,
  onboardingCompleted: false,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the authentication context and its initial values.&lt;/p&gt;

&lt;h4&gt;
  
  
  Context Provider
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function SessionProvider({ children }: PropsWithChildren) {
  const [[isSessionLoading, session], setSession] = useStorageState("session");
  const [isLoading, setLoading] = useState(false);
  const [onboardingCompleted, setOnboardingCompleted] = useState(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;SessionProvider&lt;/code&gt; wraps your app, giving access to authentication states like &lt;code&gt;session&lt;/code&gt;, &lt;code&gt;isLoading&lt;/code&gt;, and &lt;code&gt;onboardingCompleted&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Authentication Functions: Sign Up, Sign In, Sign Out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;These functions handle the core logic for user sign-up, login, and logout processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sign Up Function:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Firebase’s &lt;code&gt;auth().createUserWithEmailAndPassword()&lt;/code&gt; creates the user account.&lt;/li&gt;
&lt;li&gt;After user creation, you obtain a token (&lt;code&gt;user.user.getIdTokenResult()&lt;/code&gt;), which can be passed to your backend API to create a corresponding user account in your database.&lt;/li&gt;
&lt;li&gt;Errors are handled and appropriate messages are shown to the user.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const signUp = async (email: string, password: string) =&amp;gt; {
  if (!email &amp;amp;&amp;amp; !password) {
    showToast("warning", "Missing email and/or password");
    setLoading(false);
    return;
  }

  try {
    setLoading(true);
    const user = await auth().createUserWithEmailAndPassword(email, password);
    const token = await user.user.getIdTokenResult();

    const data = { email: email, uid: user.user.uid };
    const endpoint = "signup";
    const response = await actionProvider.makeRequest(endpoint, "POST", data, token.token);

    if (response?.success) {
      showToast("success", "Welcome! User created successfully.");
      setSession(token.token);
    } else {
      showToast("error", response?.message || "Error creating user.");
    }
  } catch (error) {
    handleSignUpError(error); // e.g., "auth/email-already-in-use"
  } finally {
    setLoading(false);
  }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sign In Function:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Uses Firebase’s &lt;code&gt;auth().signInWithEmailAndPassword()&lt;/code&gt; to sign in the user.&lt;/li&gt;
&lt;li&gt;The session token is retrieved, and the user’s state is set accordingly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const signIn = async (email: string, password: string) =&amp;gt; {
  if (!email &amp;amp;&amp;amp; !password) {
    showToast("warning", "Missing email and/or password");
    setLoading(false);
    return;
  }
  setLoading(true);

  try {
    const response = await axios.get("http://localhost:5000/api/v1/user");
    setOnboardingCompleted(!!response.data.data.completedQuestion);
    if (response.data.success) {
      setSession("your-session-token");
      showToast("success", "Successfully signed in!");
    }
  } catch (error) {
    console.log(error);
    showToast("error", "Failed to sign in");
  } finally {
    setLoading(false);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sign Out Function:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clears the session by setting &lt;code&gt;setSession(null)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Optionally calls &lt;code&gt;auth().signOut()&lt;/code&gt; to remove Firebase authentication.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const signOut = async () =&amp;gt; {
  setLoading(true);

  try {
    await auth().signOut();
    setSession(null);
    showToast("success", "Signed out successfully!");
  } catch (error) {
    showToast("error", "Failed to sign out");
  } finally {
    setLoading(false);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exposed Values in Context:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This is where the power of context lies: all the essential authentication state and functions are passed through the &lt;code&gt;value&lt;/code&gt; prop, making them available throughout the app.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  return (
    &amp;lt;AuthContext.Provider
      value={{
        signIn,
        signOut,
        signUp,
        session,
        isLoading: isLoading || isSessionLoading,
        isAuthenticated,
        onboardingCompleted,
      }}
    &amp;gt;
      {children}
    &amp;lt;/AuthContext.Provider&amp;gt;
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Providers → Auth folder → useStorageState.tsx&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useStorageState&lt;/code&gt; is a custom hook designed to manage storage in mobile. It helps persist the user’s session across app restarts, which is critical for keeping users logged in after closing the app.&lt;/p&gt;

&lt;p&gt;When a user signs in, their session token (or authentication token) is generated. This token needs to be stored securely and retrieved later when the app is reopened. &lt;code&gt;useStorageState&lt;/code&gt; handles this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g2vosy9uzkjfxq0hix0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g2vosy9uzkjfxq0hix0.png" alt="Image description" width="720" height="1391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the code. You can remove the “web” checking part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as SecureStore from "expo-secure-store";
import * as React from "react";
import { Platform } from "react-native";

type UseStateHook&amp;lt;T&amp;gt; = [[boolean, T | null], (value: T | null) =&amp;gt; void];

function useAsyncState&amp;lt;T&amp;gt;(
  initialValue: [boolean, T | null] = [true, null]
): UseStateHook&amp;lt;T&amp;gt; {
  return React.useReducer(
    (
      state: [boolean, T | null],
      action: T | null = null
    ): [boolean, T | null] =&amp;gt; [false, action],
    initialValue
  ) as UseStateHook&amp;lt;T&amp;gt;;
}

export async function setStorageItemAsync(key: string, value: string | null) {
  if (Platform.OS === "web") {
    try {
      if (value === null) {
        localStorage.removeItem(key);
      } else {
        localStorage.setItem(key, value);
      }
    } catch (e) {
      console.error("Local storage is unavailable:", e);
    }
  } else {
    if (value == null) {
      await SecureStore.deleteItemAsync(key);
    } else {
      await SecureStore.setItemAsync(key, value);
    }
  }
}

export function useStorageState(key: any): UseStateHook&amp;lt;string&amp;gt; {
  const [state, setState] = useAsyncState&amp;lt;string&amp;gt;();

  React.useEffect(() =&amp;gt; {
    if (Platform.OS === "web") {
      try {
        if (typeof localStorage !== "undefined") {
          setState(localStorage.getItem(key));
        }
      } catch (e) {
        console.error("Local storage is unavailable:", e);
      }
    } else {
      SecureStore.getItemAsync(key).then((value) =&amp;gt; {
        setState(value);
      });
    }
  }, [key]);

  const setValue = React.useCallback(
    (value: string | null) =&amp;gt; {
      setState(value);
      setStorageItemAsync(key, value);
    },
    [key]
  );

  return [state, setValue];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useStorageState&lt;/code&gt; leverages Expo SecureStore to store and retrieve sensitive data, such as a user’s session token, securely. Secure storage is critical on mobile to protect user data from unauthorized access, especially when dealing with authentication tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By using &lt;code&gt;SessionProvider&lt;/code&gt;, &lt;code&gt;expo-router&lt;/code&gt;, and hooks like &lt;code&gt;useSegments&lt;/code&gt;, we've built a flexible routing system that ensures only authenticated users can access protected areas of our app. The key is using context to share authentication state and routing logic that redirects users to the correct screens based on their authentication and onboarding status.&lt;/p&gt;

&lt;p&gt;This setup provides a robust way to manage routes and authentication in Expo, ensuring a secure and user-friendly flow for mobile apps.&lt;/p&gt;

&lt;p&gt;If you need any help → email me at &lt;a href="mailto:hello@detl.ca"&gt;hello@detl.ca&lt;/a&gt; or simply connect with me on &lt;a href="https://www.linkedin.com/in/muaz-saleh1995" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>router</category>
      <category>softwaredevelopment</category>
      <category>authentication</category>
    </item>
    <item>
      <title>How to Implement Face ID and Touch ID in React Native Expo in your Onboarding flow, Login Screen and Settings Screen</title>
      <dc:creator>DETL INC</dc:creator>
      <pubDate>Thu, 14 Nov 2024 15:14:41 +0000</pubDate>
      <link>https://dev.to/detl_inc/how-to-implement-face-id-and-touch-id-in-react-native-expo-in-your-onboarding-flow-login-screen-4k0e</link>
      <guid>https://dev.to/detl_inc/how-to-implement-face-id-and-touch-id-in-react-native-expo-in-your-onboarding-flow-login-screen-4k0e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ld59wmjzid2ibxrrdi5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ld59wmjzid2ibxrrdi5.png" alt="Image description" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://www.detl.ca" rel="noopener noreferrer"&gt;DETL&lt;/a&gt;&lt;/strong&gt;, we specialize in crafting stellar software applications that seamlessly blend hardcore engineering with sleek design. Our mission is to deliver both mobile and web applications that provide exceptional user experience without compromising on aesthetics or functionality. In this blog post we will guide you through implementing Face ID and Touch ID authentication in your React Native Expo app’s onboarding flow, login screen and settings screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Biometric authentication has become the standard in modern applications, offering users a quick, reliable and secure way to access their data. It is very important that engineers who are developing applications implement Face ID and Touch ID to enhance their apps user experience.&lt;/p&gt;

&lt;p&gt;In this blog post we will cover the following&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Required Packages to install: The essential packages you need to install.&lt;/li&gt;
&lt;li&gt;Custom Hook Creation (useSetting.tsx): How to create a custom hook for managing biometric authentication.&lt;/li&gt;
&lt;li&gt;Onboarding Implementation: Integrating Face ID and Touch ID in the onboarding screen.&lt;/li&gt;
&lt;li&gt;Login Screen Integration: Implementing biometric login in your login screen.&lt;/li&gt;
&lt;li&gt;Settings Screen Toggle: Allowing users to enable or disable biometric authentication from the settings screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Required Packages to install
&lt;/h2&gt;

&lt;p&gt;Before we tell you how to implement Face ID or Touch ID, we will require you to first install the following packages.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;expo-local-authentication:&lt;/strong&gt; Provides access to biometric authentication capabilities. &lt;code&gt;expo install expo-local-authentication&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;expo-secure-store:&lt;/strong&gt; Allows you to securely store sensitive data like user credentials. &lt;code&gt;expo install expo-secure-store&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@react-native-async-storage/async-storage:&lt;/strong&gt; A simple, unencrypted, asynchronous storage system. &lt;code&gt;yarn add @react-native-async-storage/async-storage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;expo-checkbox:&lt;/strong&gt; A customizable checkbox component. &lt;code&gt;expo install expo-checkbox&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other Dependencies:&lt;/strong&gt; Ensure you have expo-router, expo-font, and any other dependencies your project requires.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Install all dependencies together
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expo install expo-local-authentication expo-secure-store expo-checkbox @react-native-async-storage/async-storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Creating a Custom Hook: &lt;code&gt;useSettings&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To manage biometric authentication settings across your app, we’ll create a custom hook named &lt;code&gt;useSettings&lt;/code&gt;. This hook will handle enabling/disabling Face ID and Touch ID, checking available biometric types, and storing the authentication status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Hook
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;hooks&lt;/code&gt; directory, create a new file called &lt;code&gt;useSettings.tsx&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as LocalAuthentication from 'expo-local-authentication';
import { useToast } from '@/providers/useToast';

interface BiometricAuthStatus {
  isFaceIDEnabled: boolean;
  isTouchIDEnabled: boolean;
}

export const useSettings = () =&amp;gt; {
  const [biometricAuth, setBiometricAuth] = useState&amp;lt;BiometricAuthStatus&amp;gt;({
    isFaceIDEnabled: false,
    isTouchIDEnabled: false,
  });

  const [availableBiometrics, setAvailableBiometrics] = useState&amp;lt;LocalAuthentication.AuthenticationType[]&amp;gt;([]);

  const { showToast } = useToast();

  const checkAvailableBiometrics = async () =&amp;gt; {
    try {
      const biometrics = await LocalAuthentication.supportedAuthenticationTypesAsync();
      setAvailableBiometrics(biometrics);
    } catch (error) {
      console.log(error);
    }
  };

  const enableBiometricAuth = async (biometricType: 'FaceID' | 'TouchID'): Promise&amp;lt;boolean&amp;gt; =&amp;gt; {
    try {
      const isBiometricAvailable = await LocalAuthentication.hasHardwareAsync();
      if (!isBiometricAvailable) {
        showToast('error', 'Your device does not support biometric authentication.');
        return false;
      }

      const savedBiometric = await LocalAuthentication.isEnrolledAsync();
      if (!savedBiometric) {
        showToast('error', 'No biometric records found.');
        return false;
      }

      const result = await LocalAuthentication.authenticateAsync({
        promptMessage: 'Authenticate with biometrics',
        fallbackLabel: 'Enter password',
      });

      if (result.success) {
        setBiometricAuth((prevState) =&amp;gt; {
          const updatedState = { ...prevState };
          if (biometricType === 'FaceID') {
            updatedState.isFaceIDEnabled = !prevState.isFaceIDEnabled;
            showToast(
              'success',
              `Face ID ${updatedState.isFaceIDEnabled ? 'enabled' : 'disabled'} successfully.`
            );
          } else if (biometricType === 'TouchID') {
            updatedState.isTouchIDEnabled = !prevState.isTouchIDEnabled;
            showToast(
              'success',
              `Touch ID ${updatedState.isTouchIDEnabled ? 'enabled' : 'disabled'} successfully.`
            );
          }
          // Save the updated status
          AsyncStorage.setItem('biometricAuthStatus', JSON.stringify(updatedState));
          return updatedState;
        });

        return true;
      } else {
        showToast('error', 'Authentication failed.');
        return false;
      }
    } catch (error) {
      console.log(error);
      showToast('error', 'An error occurred while enabling biometric authentication.');
      return false;
    }
  };

  const checkIfBiometricEnabled = async (): Promise&amp;lt;void&amp;gt; =&amp;gt; {
    const biometricStatus = await AsyncStorage.getItem('biometricAuthStatus');
    if (biometricStatus) {
      const parsedStatus: BiometricAuthStatus = JSON.parse(biometricStatus);
      setBiometricAuth(parsedStatus);
    } else {
      setBiometricAuth({
        isFaceIDEnabled: false,
        isTouchIDEnabled: false,
      });
    }
  };

  useEffect(() =&amp;gt; {
    checkAvailableBiometrics();
    checkIfBiometricEnabled();
  }, []);

  return {
    biometricAuth,
    enableBiometricAuth,
    checkIfBiometricEnabled,
    availableBiometrics,
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;State Management:&lt;/strong&gt; We use &lt;code&gt;useState&lt;/code&gt; to manage the biometric authentication status and the available biometric types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check Available Biometrics:&lt;/strong&gt; &lt;code&gt;checkAvailableBiometrics&lt;/code&gt; checks what biometric authentication types are supported on the device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable Biometric Authentication:&lt;/strong&gt; &lt;code&gt;enableBiometricAuth&lt;/code&gt; prompts the user to authenticate using biometrics and updates the state accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persisting State:&lt;/strong&gt; We use &lt;code&gt;AsyncStorage&lt;/code&gt; to persist the biometric authentication status across app sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initialization:&lt;/strong&gt; The &lt;code&gt;useEffect&lt;/code&gt; hook initializes the available biometrics and checks if biometric authentication is enabled.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Implementing Face ID and Touch ID in the Onboarding Screen
&lt;/h2&gt;

&lt;p&gt;Next, we’ll integrate biometric authentication options into the onboarding flow, allowing users to enable Face ID or Touch ID during onboarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Onboarding Screen Component
&lt;/h3&gt;

&lt;p&gt;Create or update your onboarding screen component, for example, &lt;code&gt;FaceID_TouchID.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { View, Image, Text, StyleSheet, TouchableOpacity } from "react-native";
import GlobalButton from "@/components/shared/Button";
import Header from "@/components/shared/Header";
import HeaderTitle from "@/components/shared/HeaderTitle";
import Subtitle from "@/components/shared/Subtitle";
import { Colors, globalStyle } from "@/constants/Colors";
import { useRouter } from "expo-router";
import { useSettings } from "@/hooks/settings/useSettings";
import * as LocalAuthentication from "expo-local-authentication";

const FaceID_TouchID = () =&amp;gt; {
  const { biometricAuth, enableBiometricAuth, availableBiometrics } = useSettings();
  const router = useRouter();

  const isFaceIdAvailable = availableBiometrics.includes(
    LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION
  );

  const isTouchIDAvailable = availableBiometrics.includes(
    LocalAuthentication.AuthenticationType.FINGERPRINT
  );

  const isAnyBiometricAvailable = isFaceIdAvailable || isTouchIDAvailable;

  const handleEnableBiometricAuth = async (biometricType: 'FaceID' | 'TouchID') =&amp;gt; {
    const success = await enableBiometricAuth(biometricType);
    if (success) {
      router.push("/(auth)/OnboardingFlow/NextOnboardingScreenAfterThisOne");
    }
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header title="Enable Biometric Authentication" /&amp;gt;
      &amp;lt;HeaderTitle title={`Easy Access\nBetter Support`} /&amp;gt;
      &amp;lt;View style={styles.container}&amp;gt;
        {isAnyBiometricAvailable ? (
          &amp;lt;&amp;gt;
            {isFaceIdAvailable &amp;amp;&amp;amp; (
              &amp;lt;Image
                source={require("@/assets/images/onboarding/FaceIDImage.png")}
                resizeMode="contain"
                style={styles.image}
              /&amp;gt;
            )}
            {isTouchIDAvailable &amp;amp;&amp;amp; (
              &amp;lt;Image
                source={require("@/assets/images/TouchID.png")}
                resizeMode="contain"
                style={styles.image}
              /&amp;gt;
            )}
          &amp;lt;/&amp;gt;
        ) : (
          &amp;lt;Text style={styles.notAvailableText}&amp;gt;
            Biometric authentication is not available on this device.
          &amp;lt;/Text&amp;gt;
        )}
        {isAnyBiometricAvailable &amp;amp;&amp;amp; (
          &amp;lt;Subtitle
            style={{ marginTop: 30 }}
            subtitle="Enable biometric authentication for quick, secure, and effortless access to your support whenever you need it."
          /&amp;gt;
        )}
      &amp;lt;/View&amp;gt;
      &amp;lt;View style={styles.buttonContainer}&amp;gt;
        {isAnyBiometricAvailable &amp;amp;&amp;amp; (
          &amp;lt;&amp;gt;
            {isFaceIdAvailable &amp;amp;&amp;amp; (
              &amp;lt;GlobalButton
                title={
                  biometricAuth.isFaceIDEnabled
                    ? "Face ID Enabled"
                    : "Enable Face ID"
                }
                disabled={false}
                buttonColor={Colors.button.backgroundButtonDark}
                textColor={Colors.button.textLight}
                showIcon={false}
                onPress={() =&amp;gt; handleEnableBiometricAuth("FaceID")}
              /&amp;gt;
            )}
            {isTouchIDAvailable &amp;amp;&amp;amp; (
              &amp;lt;GlobalButton
                title={
                  biometricAuth.isTouchIDEnabled
                    ? "Touch ID Enabled"
                    : "Enable Touch ID"
                }
                disabled={false}
                buttonColor={Colors.button.backgroundButtonDark}
                textColor={Colors.button.textLight}
                showIcon={false}
                onPress={() =&amp;gt; handleEnableBiometricAuth("TouchID")}
              /&amp;gt;
            )}
          &amp;lt;/&amp;gt;
        )}
      &amp;lt;/View&amp;gt;
      &amp;lt;TouchableOpacity
        style={styles.skipButton}
        onPress={() =&amp;gt; router.push("/(auth)/OnboardingFlow/screenThirteen")}
      &amp;gt;
        &amp;lt;Text style={styles.skipText}&amp;gt;
          {isAnyBiometricAvailable ? "Enable later" : "Skip"}
        &amp;lt;/Text&amp;gt;
      &amp;lt;/TouchableOpacity&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default FaceID_TouchID;

const styles = StyleSheet.create({
  container: {
    flex: 0.9,
    alignItems: "center",
    justifyContent: "center",
  },
  image: {
    height: 250,
    width: 250,
  },
  buttonContainer: {
    marginHorizontal: globalStyle.container.marginHorizontal,
    marginBottom: 10,
  },
  skipButton: {
    justifyContent: "center",
    alignItems: "center",
  },
  skipText: {
    textAlign: "center",
    fontFamily: globalStyle.font.fontFamilyBold,
    color: Colors.button.textDark,
  },
  notAvailableText: {
    textAlign: "center",
    fontFamily: globalStyle.font.fontFamilyMedium,
    color: Colors.button.textDark,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biometric Availability:&lt;/strong&gt; We check which biometric types are available on the device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic UI:&lt;/strong&gt; The UI adapts based on the available biometrics, showing relevant images and buttons.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling Authentication:&lt;/strong&gt; When the user taps “Enable Face ID” or “Enable Touch ID”, we call &lt;code&gt;handleEnableBiometricAuth&lt;/code&gt;, which uses our custom hook to enable biometric authentication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Navigation:&lt;/strong&gt; Upon successful authentication, we navigate to the next onboarding screen which in this case we called &lt;code&gt;NextOnboardingScreenAfterThisOne&lt;/code&gt; which is your next onboarding screen&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.push("/(auth)/OnboardingFlow/NextOnboardingScreenAfterThisOne");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Implementing Face ID in the Login Screen
&lt;/h2&gt;

&lt;p&gt;We’ll integrate biometric authentication into the login screen, allowing users to log in using Face ID or Touch ID if they have previously enabled it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login Screen Component
&lt;/h3&gt;

&lt;p&gt;Update your &lt;code&gt;Login.tsx&lt;/code&gt; component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect, useRef } from "react";
import { View, Text, StyleSheet, ScrollView, Keyboard } from "react-native";
import * as SecureStore from "expo-secure-store";
import * as LocalAuthentication from "expo-local-authentication";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Feather } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import GlobalButton from "@/components/shared/Button";
import CustomTextInput from "@/components/shared/CustomTextInput";
import Header from "@/components/shared/Header";
import HeaderTitle from "@/components/shared/HeaderTitle";
import PasswordStrengthDisplay from "@/components/shared/PasswordStrengthDisplay";
import Checkbox from "expo-checkbox";
import { Colors, globalStyle } from "@/constants/Colors";
import { useSession } from "@/providers/Auth/AuthProvider";
import { useToast } from "@/providers/useToast";
import PsyvatarLogo from "@/assets/images/Logo";

const Login = () =&amp;gt; {
  const { params } = useLocalSearchParams();
  const { showToast } = useToast();
  const { signIn, isLoading, signUp } = useSession();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");
  const [securePassword, setSecurePassword] = useState(true);
  const [isChecked, setChecked] = useState(false);

  const [isBiometricSupported, setIsBiometricSupported] = useState(false);
  const [biometricType, setBiometricType] = useState("");
  const [isBiometricEnabled, setIsBiometricEnabled] = useState(false);

  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const nameRef = useRef(null);

  const isInvalidRegister = name === "" || email === "" || password === "";
  const isInvalidLogin = email === "" || password === "";

  useEffect(() =&amp;gt; {
    setTimeout(() =&amp;gt; {
      if (params === "register") {
        nameRef?.current?.focus();
      } else {
        emailRef?.current?.focus();
      }
    }, 800);

    const getEmailFromStore = async () =&amp;gt; {
      try {
        const storedEmail = await SecureStore.getItemAsync("userEmail");
        if (storedEmail) {
          setEmail(storedEmail);
          setChecked(true);
        }
      } catch (error) {
        console.log("Error fetching email from SecureStore", error);
      }
    };

    const checkBiometricSupport = async () =&amp;gt; {
      const compatible = await LocalAuthentication.hasHardwareAsync();
      setIsBiometricSupported(compatible);

      if (compatible) {
        const savedBiometrics = await LocalAuthentication.isEnrolledAsync();
        if (savedBiometrics) {
          const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
          if (
            types.includes(
              LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION
            )
          ) {
            setBiometricType("Face ID");
          } else if (
            types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT)
          ) {
            setBiometricType("Touch ID");
          } else {
            setBiometricType("Biometrics");
          }
        }
      }

      const biometricEnabled = await AsyncStorage.getItem("biometricAuthStatus");
      if (biometricEnabled) {
        const parsedStatus = JSON.parse(biometricEnabled);
        setIsBiometricEnabled(
          parsedStatus.isFaceIDEnabled || parsedStatus.isTouchIDEnabled
        );
      }
    };

    getEmailFromStore();
    checkBiometricSupport();
  }, []);

  const handleRegister = () =&amp;gt; {
    if (params === "register" &amp;amp;&amp;amp; isInvalidRegister) {
      return showToast("warning", "One or more fields are missing");
    }
    signUp(email, password, name);
  };

  const handleLogin = async () =&amp;gt; {
    if (params === "login" &amp;amp;&amp;amp; isInvalidLogin) {
      return showToast("warning", "One or more fields are missing");
    }
    Keyboard.dismiss();

    const loginSuccess = await signIn(email, password);

    if (loginSuccess) {
      if (isChecked) {
        try {
          await SecureStore.setItemAsync("userEmail", email);
        } catch (error) {
          console.log("Error saving email to SecureStore", error);
        }
      } else {
        await SecureStore.deleteItemAsync("userEmail");
      }

      if (isBiometricEnabled) {
        try {
          await SecureStore.setItemAsync("userEmail", email);
          await SecureStore.setItemAsync("userPassword", password);
        } catch (error) {
          console.log("Error saving credentials to SecureStore", error);
        }
      }
    }
  };

  const handleBiometricLogin = async () =&amp;gt; {
    try {
      const result = await LocalAuthentication.authenticateAsync({
        promptMessage: `Login with ${biometricType}`,
        fallbackLabel: "Enter password",
      });

      if (result.success) {
        const storedEmail = await SecureStore.getItemAsync("userEmail");
        const storedPassword = await SecureStore.getItemAsync("userPassword");

        if (storedEmail &amp;amp;&amp;amp; storedPassword) {
          await signIn(storedEmail, storedPassword);
        } else {
          showToast("error", "No credentials found. Please log in manually.");
          setIsBiometricSupported(false);
        }
      } else {
        showToast("error", "Biometric authentication failed.");
        setIsBiometricSupported(false);
      }
    } catch (error) {
      console.log("Biometric authentication error:", error);
      showToast("error", "An error occurred during biometric authentication.");
      setIsBiometricSupported(false);
    }
  };

  const getButtonProps = () =&amp;gt; {
    if (params === "login" &amp;amp;&amp;amp; isBiometricEnabled &amp;amp;&amp;amp; isBiometricSupported) {
      return {
        title: `Login with ${biometricType}`,
        onPress: handleBiometricLogin,
      };
    } else if (params === "login") {
      return {
        title: "Sign In",
        onPress: handleLogin,
      };
    } else {
      return {
        title: "Sign Up",
        onPress: handleRegister,
      };
    }
  };

  const { title, onPress } = getButtonProps();

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header logo={&amp;lt;Logo /&amp;gt;} BackButton /&amp;gt;
      &amp;lt;HeaderTitle
        title={`${params === "register" ? "Welcome!" : "Welcome Back!"}`}
      /&amp;gt;
      &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;ScrollView
          style={styles.container}
          showsVerticalScrollIndicator={false}
        &amp;gt;
          {params === "register" &amp;amp;&amp;amp; (
            &amp;lt;CustomTextInput
              keyboardType="default"
              label="Name"
              placeholder="Your name"
              leftIcon={
                &amp;lt;Feather
                  name="user"
                  size={Colors.button.iconSize}
                  color={Colors.button.iconColorDark}
                /&amp;gt;
              }
              secureTextEntry={false}
              onChangeText={(text) =&amp;gt; setName(text)}
              value={name}
              height={50}
              ref={nameRef}
              onSubmitEditing={() =&amp;gt; emailRef?.current?.focus()}
            /&amp;gt;
          )}
          &amp;lt;CustomTextInput
            ref={emailRef}
            keyboardType="email-address"
            label="Email address"
            placeholder="Enter your email"
            leftIcon={
              &amp;lt;Feather
                name="mail"
                size={Colors.button.iconSize}
                color={Colors.button.iconColorDark}
              /&amp;gt;
            }
            secureTextEntry={false}
            onChangeText={setEmail}
            value={email}
            autoCapitalize="none"
            height={50}
            onSubmitEditing={() =&amp;gt; passwordRef?.current?.focus()}
          /&amp;gt;
          &amp;lt;CustomTextInput
            ref={passwordRef}
            value={password}
            keyboardType="visible-password"
            label="Password"
            placeholder="Enter your password"
            leftIcon={
              &amp;lt;Feather
                name="lock"
                size={Colors.button.iconSize}
                color={Colors.button.iconColorDark}
              /&amp;gt;
            }
            rightIcon={
              &amp;lt;Feather
                name={securePassword ? "eye-off" : "eye"}
                size={Colors.button.iconSize}
                color={Colors.button.iconColorDark}
              /&amp;gt;
            }
            rightIconPressable={() =&amp;gt; setSecurePassword(!securePassword)}
            secureTextEntry={securePassword}
            onChangeText={setPassword}
            autoCapitalize="none"
            height={50}
            onSubmitEditing={() =&amp;gt; Keyboard.dismiss()}
          /&amp;gt;

          {params === "register" &amp;amp;&amp;amp; (
            &amp;lt;PasswordStrengthDisplay password={password} /&amp;gt;
          )}

          &amp;lt;View style={styles.rememberMeContainer}&amp;gt;
            &amp;lt;Checkbox
              style={styles.checkbox}
              value={isChecked}
              onValueChange={setChecked}
              color={isChecked ? "#4096C1" : undefined}
            /&amp;gt;
            &amp;lt;Text style={styles.rememberText}&amp;gt;Remember me&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/ScrollView&amp;gt;

        &amp;lt;View style={styles.buttonWrapper}&amp;gt;
          &amp;lt;GlobalButton
            title={title}
            onPress={onPress}
            disabled={isLoading}
            loading={isLoading}
            buttonColor={Colors.button.backgroundButtonDark}
            textColor={Colors.button.textLight}
            showIcon={false}
          /&amp;gt;

       {params === "login" &amp;amp;&amp;amp; isBiometricEnabled &amp;amp;&amp;amp; isBiometricSupported &amp;amp;&amp;amp; (
            &amp;lt;TouchableOpacity
              onPress={() =&amp;gt; setIsBiometricSupported(false)}
              style={{
                margin: 10,
              }}
            &amp;gt;
              &amp;lt;Text
                style={{
                  textAlign: "center",
                  fontFamily: globalStyle.font.fontFamilyBold,
                  color: Colors.button.textDark,
                }}
              &amp;gt;
                Sign In manually
              &amp;lt;/Text&amp;gt;
            &amp;lt;/TouchableOpacity&amp;gt;
          )}
        &amp;lt;/View&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Login;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginHorizontal: globalStyle.container.marginHorizontal,
  },
  rememberText: {
    color: "#282545",
    fontWeight: "400",
    fontSize: 14,
  },
  rememberMeContainer: {
    marginVertical: 10,
    marginBottom: 0,
    flexDirection: "row",
    alignItems: "center",
  },
  checkbox: {
    margin: 8,
    borderRadius: 4,
  },
  buttonWrapper: {
    marginHorizontal: globalStyle.container.marginHorizontal,
    marginBottom: 15,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biometric Support Check&lt;/strong&gt;: On component mount, we check if the device supports biometric authentication and if it’s enabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biometric Login Handling&lt;/strong&gt;: &lt;code&gt;handleBiometricLogin&lt;/code&gt; manages the biometric authentication flow. If successful, it retrieves stored credentials and logs the user in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Button Rendering&lt;/strong&gt;: Based on whether biometric login is available, we dynamically set the button’s title and &lt;code&gt;onPress&lt;/code&gt; handler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Storing Credentials&lt;/strong&gt;: Upon a successful manual login, if biometric authentication is enabled, we securely store the user’s credentials for future biometric logins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We have a button on the bottom of the file where we allow a user to toggle whether they want to sign in manually, which is a fail-safe approach in case biometric authentication doesn’t work.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Adding Biometric Authentication Toggle in the Settings Screen
&lt;/h2&gt;

&lt;p&gt;To give users control over biometric authentication, we’ll add toggle switches in the settings screen, allowing them to enable or disable Face ID or Touch ID at any time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings Screen Component
&lt;/h3&gt;

&lt;p&gt;Update your &lt;code&gt;SettingsScreen.tsx&lt;/code&gt; component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  Image,
  Modal,
  Linking,
} from "react-native";
import { Feather, FontAwesome } from "@expo/vector-icons";
import { SettingOption, SettingToggle } from "./components";
import { useSession } from "@/providers/Auth/AuthProvider";
import GlobalButton from "@/components/shared/Button";
import { Colors, globalStyle } from "@/constants/Colors";
import HeaderTitle from "@/components/shared/HeaderTitle";
import Subtitle from "@/components/shared/Subtitle";
import AnimatedWrapper from "@/components/shared/animation";
import { useRouter } from "expo-router";
import { useSettings } from "@/hooks/settings/useSettings";
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
import { useHandleApiError } from "@/hooks/settings/useHandleApiError";
import * as LocalAuthentication from "expo-local-authentication";

export const BASE_DELAY = 50;

export default function SettingsScreen() {
  const { signOut, deleteUserRecord, isLoading } = useSession();
  const { biometricAuth, enableBiometricAuth, availableBiometrics } = useSettings();

  const isFaceIDAvailable = availableBiometrics.includes(
    LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION
  );

  const isTouchIDAvailable = availableBiometrics.includes(
    LocalAuthentication.AuthenticationType.FINGERPRINT
  );

  // Set up biometric toggle based on availability
  let biometricLabel = "";
  let isBiometricEnabled = false;
  let toggleBiometricAuth = null;
  let biometricIcon = null;

  if (isFaceIDAvailable) {
    biometricLabel = "Face ID";
    isBiometricEnabled = biometricAuth.isFaceIDEnabled;
    toggleBiometricAuth = () =&amp;gt; enableBiometricAuth("FaceID");
    biometricIcon = (
      &amp;lt;MaterialCommunityIcons
        name="face-recognition"
        size={20}
        color="#4096C1"
      /&amp;gt;
    );
  } else if (isTouchIDAvailable) {
    biometricLabel = "Touch ID";
    isBiometricEnabled = biometricAuth.isTouchIDEnabled;
    toggleBiometricAuth = () =&amp;gt; enableBiometricAuth("TouchID");
    biometricIcon = (
      &amp;lt;MaterialCommunityIcons name="fingerprint" size={20} color="#4096C1" /&amp;gt;
    );
  }

  const router = useRouter();

  const [isPushEnabled, setIsPushEnabled] = useState(false);

  const togglePushNotifications = () =&amp;gt; setIsPushEnabled(!isPushEnabled);

  const [modalVisible, setModalVisible] = useState(false);
  const handleApiError = useHandleApiError();

  const handleDelete = () =&amp;gt; {
    setModalVisible(true);
  };

  const deleteAccount = async () =&amp;gt; {
    try {
      const response = await deleteUserRecord();
      if (response.success) {
        setTimeout(() =&amp;gt; {
          setModalVisible(!modalVisible);
        }, 500);
      } else {
        handleApiError(response);
      }
    } catch (error) {
      console.log(error);
    }
  };

  const cancelDelete = () =&amp;gt; {
    setModalVisible(!modalVisible);
  };

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;ScrollView showsVerticalScrollIndicator={false}&amp;gt;
        &amp;lt;AnimatedWrapper delay={BASE_DELAY * 1}&amp;gt;
          &amp;lt;Text style={styles.sectionTitle}&amp;gt;GENERAL&amp;lt;/Text&amp;gt;
        &amp;lt;/AnimatedWrapper&amp;gt;
        &amp;lt;SettingOption
          delay={BASE_DELAY * 2}
          label="Get Help"
          icon={
            &amp;lt;FontAwesome name="question-circle-o" size={20} color="#4096C1" /&amp;gt;
          }
          onPress={() =&amp;gt; router.navigate("/(auth)/help")}
        /&amp;gt;
        &amp;lt;SettingOption
          delay={BASE_DELAY * 3}
          label="Contact Us"
          icon={&amp;lt;Feather name="mail" size={20} color="#4096C1" /&amp;gt;}
          onPress={() =&amp;gt; router.navigate("/(auth)/help/contact_us")}
        /&amp;gt;
        &amp;lt;SettingOption
          delay={BASE_DELAY * 4}
          label="Subscription Details"
          subLabel="View current plan &amp;amp; upgrade"
          icon={&amp;lt;FontAwesome name="credit-card" size={20} color="#4096C1" /&amp;gt;}
          onPress={() =&amp;gt; console.log("Subscription Details Pressed")}
        /&amp;gt;

        &amp;lt;SettingOption
          delay={BASE_DELAY * 5}
          label="Select/Change Therapist"
          subLabel="Select or change your therapist"
          icon={&amp;lt;Feather name="user" size={20} color="#4096C1" /&amp;gt;}
          onPress={() =&amp;gt; router.push("/(auth)/OnboardingFlow/selectAvatar")}
        /&amp;gt;

        &amp;lt;SettingOption
          delay={BASE_DELAY * 5}
          label="Join Psyvatar community"
          subLabel="Join our discord community"
          icon={
            &amp;lt;MaterialCommunityIcons
              name="account-group-outline"
              size={20}
              color="#4096C1"
            /&amp;gt;
          }
          onPress={() =&amp;gt; Linking.openURL("https://discord.gg/dcBzhh5e")}
        /&amp;gt;

        &amp;lt;AnimatedWrapper delay={BASE_DELAY * 6}&amp;gt;
          &amp;lt;Text style={styles.sectionTitle}&amp;gt;NOTIFICATIONS&amp;lt;/Text&amp;gt;
        &amp;lt;/AnimatedWrapper&amp;gt;
        &amp;lt;AnimatedWrapper delay={BASE_DELAY * 7}&amp;gt;
          &amp;lt;SettingToggle
            label="Push Notifications"
            subLabel="For daily updates and others."
            icon={&amp;lt;Feather name="bell" size={20} color="#4096C1" /&amp;gt;}
            isEnabled={isPushEnabled}
            toggleSwitch={togglePushNotifications}
          /&amp;gt;
        &amp;lt;/AnimatedWrapper&amp;gt;
        {biometricLabel !== "" &amp;amp;&amp;amp; (
          &amp;lt;AnimatedWrapper delay={BASE_DELAY * 8}&amp;gt;
            &amp;lt;SettingToggle
              label={biometricLabel}
              subLabel={`Enable or disable ${biometricLabel}`}
              icon={biometricIcon}
              isEnabled={isBiometricEnabled}
              toggleSwitch={toggleBiometricAuth}
            /&amp;gt;
          &amp;lt;/AnimatedWrapper&amp;gt;
        )}
        &amp;lt;AnimatedWrapper delay={BASE_DELAY * 9}&amp;gt;
          &amp;lt;Text style={styles.sectionTitle}&amp;gt;MORE&amp;lt;/Text&amp;gt;
        &amp;lt;/AnimatedWrapper&amp;gt;
        &amp;lt;SettingOption
          delay={BASE_DELAY * 10}
          label="Logout"
          icon={&amp;lt;Feather name="log-out" size={24} color="#4096C1" /&amp;gt;}
          onPress={signOut}
        /&amp;gt;

        &amp;lt;AnimatedWrapper delay={BASE_DELAY * 11}&amp;gt;
          &amp;lt;GlobalButton
            title="Delete Account"
            disabled={false}
            buttonColor={"red"}
            textColor={Colors.button.textLight}
            showIcon={false}
            onPress={handleDelete}
          /&amp;gt;
        &amp;lt;/AnimatedWrapper&amp;gt;
      &amp;lt;/ScrollView&amp;gt;

      &amp;lt;Modal
        animationType="fade"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() =&amp;gt; setModalVisible(false)}
      &amp;gt;
        &amp;lt;View style={styles.centeredView}&amp;gt;
          &amp;lt;View style={styles.modalView}&amp;gt;
            &amp;lt;Image
              resizeMode="contain"
              style={{
                width: 200,
                height: 200,
                marginHorizontal: "auto",
              }}
              source={require("@/assets/images/DeleteAccountImage.png")}
            /&amp;gt;
            &amp;lt;HeaderTitle title="You sure?" /&amp;gt;
            &amp;lt;Subtitle
              subtitle="We suggest that you log out but if you insist on deleting your account we will be here if you need any mental health support."
              style={{
                marginTop: -10,
              }}
            /&amp;gt;

            &amp;lt;GlobalButton
              title="Delete"
              disabled={isLoading}
              buttonColor={"#FB6C6C"}
              textColor={Colors.button.textLight}
              showIcon={false}
              onPress={deleteAccount}
              loading={isLoading}
            /&amp;gt;

            &amp;lt;TouchableOpacity onPress={cancelDelete}&amp;gt;
              &amp;lt;Text style={styles.cancelText}&amp;gt;Cancel&amp;lt;/Text&amp;gt;
            &amp;lt;/TouchableOpacity&amp;gt;
          &amp;lt;/View&amp;gt;
        &amp;lt;/View&amp;gt;
      &amp;lt;/Modal&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "white",
    paddingHorizontal: 16,
  },
  sectionTitle: {
    fontSize: 14,
    fontWeight: "600",
    color: "#4096C1",
    marginTop: 20,
    marginBottom: 10,
    fontFamily: globalStyle.font.fontFamilyMedium,
  },

  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "rgba(0, 0, 0, 0.85)",
  },

  modalView: {
    width: "85%",
    backgroundColor: "white",
    borderRadius: 20,
    padding: 20,
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  cancelText: {
    marginTop: 10,
    color: "#7B7B7A",
    fontFamily: globalStyle.font.fontFamilyBold,
    textAlign: "center",
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biometric Toggle Setup&lt;/strong&gt;: We check if Face ID or Touch ID is available and set up the toggle accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Rendering&lt;/strong&gt;: If biometric authentication is available, we render a &lt;code&gt;SettingToggle&lt;/code&gt; component to allow users to enable or disable it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using Custom Hook&lt;/strong&gt;: The &lt;code&gt;enableBiometricAuth&lt;/code&gt; function from our &lt;code&gt;useSettings&lt;/code&gt; hook is used to handle the toggle action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI Components&lt;/strong&gt;: The settings screen includes various other settings options, and the biometric toggle is integrated seamlessly among them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integrating the Toggle Component
&lt;/h3&gt;

&lt;p&gt;Ensure you have a &lt;code&gt;SettingToggle&lt;/code&gt; component that accepts the necessary props:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// components/SettingToggle.tsx

import React from "react";
import { View, Text, StyleSheet, Switch } from "react-native";
import { globalStyle } from "@/constants/Colors";
import AnimatedWrapper from "@/components/shared/animation";

interface SettingToggleProps {
  label: string;
  subLabel?: string;
  icon?: JSX.Element;
  isEnabled: boolean;
  toggleSwitch: () =&amp;gt; void;
  delay?: number;
}

export const SettingToggle = ({
  label,
  subLabel,
  icon,
  isEnabled,
  toggleSwitch,
  delay,
}: SettingToggleProps) =&amp;gt; {
  return (
    &amp;lt;AnimatedWrapper delay={delay}&amp;gt;
      &amp;lt;View style={styles.optionContainer}&amp;gt;
        {icon &amp;amp;&amp;amp; &amp;lt;View style={styles.optionIcon}&amp;gt;{icon}&amp;lt;/View&amp;gt;}
        &amp;lt;View style={styles.optionTextContainer}&amp;gt;
          &amp;lt;Text style={styles.optionLabel}&amp;gt;{label}&amp;lt;/Text&amp;gt;
          {subLabel &amp;amp;&amp;amp; &amp;lt;Text style={styles.optionSubLabel}&amp;gt;{subLabel}&amp;lt;/Text&amp;gt;}
        &amp;lt;/View&amp;gt;
        &amp;lt;Switch
          trackColor={{ false: "#767577", true: "#4096C1" }}
          thumbColor="#f4f3f4"
          ios_backgroundColor="#3e3e3e"
          onValueChange={toggleSwitch}
          value={isEnabled}
          style={{ transform: [{ scaleX: 0.75 }, { scaleY: 0.75 }] }}
        /&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/AnimatedWrapper&amp;gt;
  );
};

const styles = StyleSheet.create({
  optionContainer: {
    flexDirection: "row",
    alignItems: "center",
    paddingVertical: 15,
    borderBottomWidth: 1,
    borderBottomColor: "#EDEDED",
    marginVertical: 15,
  },
  optionIcon: {
    marginRight: 15,
  },
  optionTextContainer: {
    flex: 1,
  },
  optionLabel: {
    fontSize: 16,
    fontWeight: "500",
    fontFamily: globalStyle.font.fontFamilyMedium,
  },
  optionSubLabel: {
    fontSize: 12,
    color: "#7C7C7C",
    fontFamily: globalStyle.font.fontFamilyMedium,
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Toggle Functionality&lt;/strong&gt;: The &lt;code&gt;Switch&lt;/code&gt; component is used to toggle biometric authentication on or off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic Props&lt;/strong&gt;: The component accepts dynamic props like &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;subLabel&lt;/code&gt;, &lt;code&gt;icon&lt;/code&gt;, &lt;code&gt;isEnabled&lt;/code&gt;, and &lt;code&gt;toggleSwitch&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Animation&lt;/strong&gt;: An &lt;code&gt;AnimatedWrapper&lt;/code&gt; is used for entry animations, enhancing the user experience.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Integrating Face ID and Touch ID into your React Native Expo app enhances security and provides a seamless user experience. By following this guide, you’ve learned how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install and set up necessary packages.&lt;/li&gt;
&lt;li&gt;Create a custom hook to manage biometric authentication settings.&lt;/li&gt;
&lt;li&gt;Implement biometric options in your onboarding flow.&lt;/li&gt;
&lt;li&gt;Enable biometric login in your login screen.&lt;/li&gt;
&lt;li&gt;Add toggle switches in the settings screen to allow users to control biometric authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://www.detl.ca" rel="noopener noreferrer"&gt;DETL&lt;/a&gt;&lt;/strong&gt;, we believe in combining robust engineering with elegant design to create exceptional software solutions. Implementing biometric authentication is just one way to elevate your app’s user experience while maintaining high security standards.&lt;/p&gt;

&lt;p&gt;Thank you for reading! If you’re interested in more tips and tutorials on building high-quality applications, stay tuned to our blog at &lt;a href="https://www.detl.ca" rel="noopener noreferrer"&gt;DETL&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>reactnative</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
