Donald Hruska

Cofounder & VP Engineering, Draftbit
๐Ÿ‘‹

Authentication with Bubble using Settable Global Variables

admin ๐Ÿ‘‹
In this guide we'll create signup and login components against the Bubble API, store the resulting API token, and pull in user profile information using that stored token. To do this we'll use "Device Variables", a special kind of global variable. For the specifics on how they variables work, check out the API Documentation. The gist is that they are settable and readable within custom code, and are persisted on a user's device across sessions.

Create Device Variable

First, we need to create a new Device Variable. Let's call it "token". You can optionally specify a default value. During development of your app's screens, you probably want to do this in order to be able to pull data back from your API. You can obtain this token by hitting the signup or login endpoint of your Bubble API outside of Draftbit (via Postman, cURL, or some other tool).
๏ปฟ
๏ปฟ

Store Token into Device Variable using Custom Code

Next, let's create signup and login form components using the Custom Components feature. I've created this basic form component that's used by both the exported Signup and Login components. You'll need to drop in your Bubble app's API URL (I'm using the Bubble test API here; you can use your Bubble app's test or production API), and to make sure that you've created "signup" and "login" endpoints in your Bubble backend workflow section. Feel free to edit the input and button components however you see fit to make them look how you desire. An easy way to do this is to build them in Draftbit and then copy the corresponding code from the View Code menu.

import React from "react";
import { Text, View, Alert } from "react-native";
import { TextField, Button } from "@draftbit/ui";
import { useNavigation } from '@react-navigation/native';
import * as GlobalVariables from "./config/GlobalVariableContext";

const BASE_API_URL =
  "https://some-bubble-app.bubbleapps.io/version-test/api/1.1";

const signup = async ({ email, password, callback }) => {
  const result = await fetch(`${BASE_API_URL}/wf/signup`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email,
      password,
    }),
  }).then((res) => res.json());

  // Note, we have to look at non-200 status codes here
  if (result.statusCode && result.statusCode !== 200) {
    Alert.alert(result.message);
  }

  if (result?.response?.token) {
    callback("Bearer " + result?.response?.token);
  }
};

const login = async ({ email, password, callback }) => {
  try {
    const result = await fetch(`${BASE_API_URL}/wf/login`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        email,
        password,
      }),
    }).then((res) => res.json());

    // Note, we have to look at non-200 status codes here
    if (result.statusCode && result.statusCode !== 200) {
      Alert.alert(result.message);
    }

    if (result?.response?.token) {
      callback("Bearer " + result?.response?.token);
    }
  } catch (err) {
    console.log("err", err);
  }
};

const Form = ({ action, navigation }) => {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  const setGlobalVariable = GlobalVariables.useSetValue();

  return (
    <View style={{ flex: 1, justifyContent: "space-between" }}>
      <View>
        <TextField
          autoCapitalize="none"
          keyboardAppearance="default"
          type="underline"
          value={email}
          label="Email Address"
          leftIconMode="inset"
          onChangeText={(email) => setEmail(email)}
          keyboardType="email-address"
          leftIconName="MaterialCommunityIcons/email-outline"
        />
        <TextField
          autoCapitalize="none"
          keyboardAppearance="default"
          type="underline"
          value={password}
          label="Password"
          leftIconMode="inset"
          onChangeText={(password) => setPassword(password)}
          keyboardType="email-address"
          leftIconName="MaterialCommunityIcons/email-outline"
          secureTextEntry={true}
        />
      </View>
      <Button
        onPress={() => {
          const callback = (token) => {
            setGlobalVariable({ key: "token", value: token });
            navigation.navigate("Main"); // Or whatever navigator or screen your app has that you'd like to proceed to next
          }

          action({
            email,
            password,
            callback,
          });
        }}
        type="solid"
      >
        {`Continue โ†’`}
      </Button>
    </View>
  );
};

export const SignupForm = ({ navigation }) => <Form action={signup} navigation={navigation} />;
export const LoginForm = ({ navigation }) => <Form action={login} navigation={navigation} />;

There's a couple important things to note here:
  • We're importing the GlobalVariables object from the GlobalVariablesContext file. This allows us to use the "setGlobalVariable" hook. This is what saves a value to our "token" Device Variable that we created in the first step. As mentioned, this value is persisted across sessions, so if your user is authenticated, that token will still be present in subsequent sessions.
  • We have to prepend "Bearer " to the token because that's the format that Bubble expects later on when we use the token.
  • We're using the nice error messages returned from the Bubble API along with the react-native "Alert" function. This will help our users understand if they did something wrong during the signup or onboarding process.
  • We're passing a "navigation" prop into the SignupForm and LoginForm components. Since this code is referred to by adding a "Custom Code" component to our app (see the documentation for that feature for more information), we'll need to pass that "navigation" prop in:๏ปฟ
    ๏ปฟ

Utilize Device Variable in API Request

After adding the Signup and Login components to the corresponding screens using the Custom Code component, it's time to use this token in a request.

Now, we need to create a Bubble service and endpoint in the Data section within Draftbit. When configuring our service or endpoint, we can use the "token" Device variable during our header configuration:
I'm going to create an endpoint to fetch the user. Be aware that the values returned from the Bubble API are dependent on the privacy settings you've configured within their platform. I've configured the privacy settings of the User data in my Bubble app to only return the user that is currently authenticated.
๏ปฟ
๏ปฟ
Now we can set up that endpoint within Draftbit. Since we have a default value set for the Device variable, that will be used when developing our screen in Draftbit. I've configured the name, email, and location fields to map to Text components using the endpoint we just created. See the REST/Fetch documentation for more.
๏ปฟ
๏ปฟ
Now, we've done it! We're pulling in authenticated data based on the Device variable's value. If you go into preview mode (either in your browser - the third button above the phone frame - or on your mobile device), you should be able to sign up or log into your bubble app, and then see data relevant to your user based on the variable that was saved in the first step.

Bonus Points: Log out button

This would be a nice addition to my app! This is easy to do with custom code - we just use the same setGlobalVariable function, but set the value of the token to null, and then navigate back to our app's initial screen. This again goes in the "Custom Code" -> "Components" section, and again is referred to by using a "Custom Code" component in whichever screen you want a log out button.
export const Logout = () => {
  const setGlobalVariable = GlobalVariables.useSetValue();
  const navigation = useNavigation();
  
  const onPress = () => {
    setGlobalVariable({ key: "token", value: null });
    navigation.navigate("Auth");
  };
  
  return <Button onPress={onPress}>Log out</Button>;
};

That's it! If you have any questions or would like additional guides for other services, please leave a comment!
Like Comment

Custom Code Kickoff Recording

admin ๐Ÿ‘‹
Thanks to those of you initial beta testers that were able to join us this morning. For those of you that weren't available or have joined the beta testing program since, we have a recording of the session available here, with the passcode S&.0ZuX5

You can find the code for the finished Camera component we built in the session at the end of this guide.
Like Comment

๐Ÿงต Nested Strings in Arrays

admin ๐Ÿ‘‹
Some APIs return data nested as the first item in an array. We've added the ability to map fields nested in arrays to components within Draftbit. Airtable is one API that uses this pattern. Now, "Link to another record" and "Lookup" Airtable fields work within Draftbit!
๏ปฟ
๏ปฟ
When using record linking in Airtable, the id of another table can now be used in a Fetch response. Those ids can be passed as navigation params, which then in turn can be used as variables into subsequent Fetch requests on other screens. This is an important step in creating apps with "list" screens that navigate to "detail" screens.
With Airtable lookup fields, data from linked records can be displayed and fetched in a single Airtable table and request, simplifying the Rest API setup required within Draftbit.
Like Comment

๐Ÿ—‘๏ธ Delete & Restore

admin ๐Ÿ‘‹
๏ปฟ ๏ปฟ

When screens and components are deleted in Draftbit, historically, those actions have been permanent. Losing work unintentionally can be very frustrating, so we sought a way to allow recovering deleted screens and components.
This feature introduces a few things. First, we now have a new section in the screens and components lists showing recently deleted items. The items can be restored with as little as a button click.
๏ปฟ
๏ปฟ
We also sought to streamline our users workflow further by introducing a variety of keyboard shortcuts. CTRL+Z is now a shortcut for undoing component deletion or any other component tree-related actions โ€” component adds, deletes, and movements. Arrow keys can now be used to navigate around the tree, and components can be copied and pasted with CTRL+C and CTRL+V.
๏ปฟ
๏ปฟ
Finally, we tackled overall performance of the tree itself. Actions such as deleting components should now feel significantly faster, leading to a smoother overall experience.