Transcript
Over the next few lessons, we're going to learn how we can have users attach images—like one that they might take from their phone or images from their photo library—which will, of course, then appear within their diary entry.
So, we're going to start in this lesson with the basics of accessing these device resources from our application.
Now, let's start with the interface. We're going to start by adding in these buttons that are going to serve as the triggers for requesting access to our users' camera or photo library.
Within our diary entry, create an edit view here. I'm going to add a group to start with. I'll add it inside of this group form, right at the top. This is going to be our "group image."
One of the things we have to decide when we're displaying images on mobile is how much real estate—how high—we want the element that’s going to show the image to occupy. We don't want the image to be so tall that it pushes everything else on the page down. So, we want to fix the space or pre-allocate the space on our screen where we're going to display the image.
We're actually going to use this group image to display back to the user the image they've selected. I'm going to allow it to stretch the width of the user's screen, which is fine. But I'm going to fix the height at 320 pixels.
I also want this group to look like my other inputs, which, of course, have a little bit of a border around them. So, I'm going to mimic the style properties of my existing inputs on this group. I’ll give it a border of two pixels, assign it the gray 30 color style, and add a little roundness so that it starts to look like our other inputs.
This sets us in good stead for adding a button to this group. This button will ask the user to take a photo and attach it to their diary entry. However, I don't want it to have the primary orange background because that would clash with our "Create Diary Entry" button.
Generally speaking, we only want to use our primary color in one place within a view because that's where the user's eye will be drawn—it signals the most important action. If we have more than one primary action, it splits focus and potentially confuses the user.
From a UX perspective, we should deemphasize this button slightly. We'll change its background to our background color variable—a very light gray, almost white. We'll then add a darker color for the button label, so it stands out slightly.
I also want this button to have an icon. We'll choose "icon and label" and select the camera icon from the Phosphor library. Make sure to change the icon color to be visible. For this button, I’ll stack the icon on top of the label, reduce the gap slightly, and increase the overall height to 80 pixels so it sits nicely in the view.
If we look at what we’re building, we have two buttons: one to choose an image from the user's photo library and the other to take a photo with the camera. Let's create the second button.
We can duplicate the first button. Before doing that, let's capture the style we just created. Detach the overridden primary style and create a new style—“Filled Light Secondary.” The "light" indicates it’s designed for a light background. Reset the icon if needed, then duplicate the button for the photo library and change the icon to the images icon.
To improve design, we can center these buttons within the group image. Hold shift, select both, right-click, and group them in a column layout. Rename this group "Group Image Buttons." Now, from the perspective of the parent container, we can center this child group, add gap spacing of 16, and add margin for breathing room.
Checking the view, it looks fine. One minor issue: the page currently sets focus on the title input when opened. Since the first action is to add an image, not a title, we should remove that focus. Go to the workflow tab, find the "set focus" action, and delete it.
Now, let's add the logic for what happens when the user taps one of these buttons. Start with taking a photo. Add a workflow for this button. The action to access the camera is under "Native Mobile → Open Camera." You have options like "Save to camera library," which also saves the photo to the device. Ignore "Make this file private" for now; we'll address it later.
The first time this is run, a modal will pop up asking the end user to grant permission for your app to access the camera. This follows our model from the last lesson: the app requests access to a resource, and the OS asks the user to allow it.
We can edit this message later for context, which is crucial before going live, otherwise app stores may reject the app. For now, hit “Allow.” Since we ticked "Save to camera library," permission for the photo library is also requested—allow full access.
Now, we can access the device’s camera via the app. Take a photo and retake if needed. At this point, nothing appears on screen because the workflow doesn't yet handle the output.
What happens behind the scenes? Go to the Data tab → File Manager. This shows all files uploaded to the app. You can see existing images, like the background on the home view or the logo. Bubble stores all uploaded files in the app's file storage—a kind of warehouse for files, separate from structured data.
The latest image uploaded from the phone is in file storage but not attached to a diary entry. We need a place to store it. Add a new field to the diary entry data type, e.g., “Image,” of type image.
Back in the diary entry form, we need to save this image. The open camera action has an output—the uploaded image—which can be used as a data source in downstream actions, like "Create a New Diary Entry."
We don’t want to create a new diary entry yet since the save button already handles that. Instead, we can store the output in a temporary container—a custom state. Attach the custom state to the view, call it “Image,” and set its type to image. Use "Element Actions → Set State" to populate this custom state with the uploaded image.
Now, after uploading an image, it lives in the custom state. When the user hits "Save," we can set the diary entry’s image field to the custom state’s value, whether creating or editing a diary entry.
Test this by taking a photo and creating a test diary entry. In the Data tab → App Data, the new diary entry should have an image field populated. Clicking the field opens the uploaded image in file storage.
What we store in the diary entry image field isn’t the file itself, but the URL of the file in our app's storage. Without storing the URL in a data type, the file would exist in storage but be unusable.
Next, set up the action for adding a photo from the user’s photo library. Use "Native Mobile → Open Camera Library." Select a single photo for simplicity. Save the output to the same custom state.
The result: clicking "Choose from Library" allows the user to select an image, which then populates the database when creating a diary entry.
However, there’s a potential issue: if a user uploads an image and closes the view without saving, the file remains in storage. These files aren’t small; over time, they could fill storage and force an upgrade.
A bigger issue: without ticking "Make this file private," images are publicly accessible online—anyone with the URL can view them. This may be fine for social apps but could be a serious privacy concern for other applications.
Toggling "Make this file private" restricts access—only the diary entry creator can view the image. But this requires the diary entry to already exist, so we can attach the image when uploading.
Thus, the workflow logic must be amended: when a user uploads an image, a diary entry must already exist to attach it securely. In the next lesson, we’ll learn how to set up a draft diary entry to enable this.