Integration guide for creating local-first apps with FlutterFlow and PowerSync with Supabase as the backend.
Used in conjunction with FlutterFlow, PowerSync enables developers to build local-first apps that are robust in poor network conditions and that have highly responsive frontends while relying on Supabase for their backend. This guide walks you through configuring PowerSync within your FlutterFlow project that has Supabase integration enabled.
New and Improved integration: Welcome to our updated FlutterFlow integration guide. This version introduces a dedicated PowerSync FlutterFlow Library, offering a simpler and more robust solution compared to the previous version which required extensive custom code.
Key improvements are:
Note that using libraries in FlutterFlow requires being on a paid plan with FlutterFlow. If this is not an option for you, you can use our legacy guide with custom code to integrate PowerSync in your FlutterFlow project.
This guide uses Supabase as the backend database provider for its seamless integration with PowerSync. However, you can integrate a different backend using custom actions. For more information, refer to the Custom backend connectors section.
Before starting this guide, you’ll need:
This guide walks you through building a basic item management app from scratch and takes about 30-40 minutes to complete. You should then be able to use this knowledge to build and extend your own app.
lists
table. The demo app will have access to this table even while offline. Run the below SQL statement in your Supabase SQL Editor:
SELECT
privilege, and for the publication mentioned in the next step (as well as for any other publications that may exist).If you’ve previously created an instance in your project, you can create an additional instance by navigating to Manage instances and clicking Create new instance:
You can also create an entirely new project with its own set of instances. Click on the PowerSync icon in the top left corner of the Dashboard or on Admin Portal at the top of the Dashboard, and then click on Create Project.
Next
version of the Service, which may contain early access or experimental features. Always use the Stable
version in production.From your Supabase Dashboard, select Connect in the top navigation bar (or follow this link):
In the Direct connection section, copy the complete connection string (including the [YOUR-PASSWORD]
placeholder)
Back in the PowerSync Dashboard, paste the connection string into the URI field. PowerSync will automatically parse this URI to populate the database connection details.
Update the Username and Password fields to use the powersync_role
and password you created when configuring your Supabase for PowerSync (see Source Database Setup).
Note: PowerSync includes Supabase’s CA certificate by default, so you can use verify-full
SSL mode without additional configuration.
Your connection settings should look similar to this:
PowerSync will now create an isolated cloud environment for your instance. This typically takes a minute or two.
You can update your instance settings by navigating to the Manage instances workspace, opening your instance options and selecting Edit instance.
Sync Rules allow developers to control which data gets synced to which user devices using a SQL-like syntax in a YAML file. For the demo app, we’re going to specify that each user can only see their own lists.
To update your Sync Rules, open the sync-rules.yaml
file.
Replace the sync-rules.yaml
file’s contents with the below:
In the top right, click “Validate sync rules” and ensure there are no errors. This validates your sync rules against your Postgres database.
In the top right, click “Deploy sync rules” and select your instance.
Confirm in the dialog and wait a couple of minutes for the deployment to complete.
anon
public
in your Supabase dashboard.powersync_core:1.3.0
:This version of powersync_core
is required for running FlutterFlow on Web.
Login
.Login
page you just created.Click on 'Test'
Checkpoint: You should now be able to log into the app using the Supabase user account you just created. After logging in you should see a blank screen.
For once, a blank screen means success!
We will now create our first UI and bind it to the data in the local SQLite database on the device.
There are three ways to read data from the SQLite database using PowerSync’s FlutterFlow library:
PowerSyncQuery
component.PowerSyncStateUpdater
component.PowerSyncQueryOnce
custom action.For reading data in FlutterFlow, you need a Custom Function per Supabase table to map Supabase rows to data that can be used by the library. This is because FlutterFlow Libraries do not support Supabase classes.
supabaseRowsToList
(if your Supabase table name is “Customers”, you would name this supabaseRowsToCustomers
).Supabase Row
lists
.supabaseRows
Custom function to map the lists table from Supabase
Under the “Page Selector”, click “Add Page, Component, or Flow”.
Select the “New Component” tab.
Select “Create Blank” and call the component ListItems
.
Under the “Widget Palette”, drag a “ListView” widget into the ListItems
component.
Still under the “Widget Palette”, drag a “ListTile” into the ListView
widget.
The ListItems component should now look like this
Under the “Widget Tree”, select the ListItems
component.
lists
.Supabase Row
.lists
.Still under the “Widget Tree”, select the “ListView” widget.
listItem
.lists
).The ListItems component should now look like this
Still under the “Widget Tree”, select the ListTile
widget.
Set the list item title text
Repeat Step 8 above for the “Subtitle”, setting it to “created_at”.
The ListItems component should now look like this
Under the “Page Selector”, select your HomePage
.
Under the “Widget Palette”, select the “Components and custom widgets imported from library projects” panel.
Drag the PowerSyncQuery
library component into your page.
In the Properties panel on the right, under “Component Parameters” -> “child”:
ListItems
we previously created.lists
.supabaseRowsToList
we created previously.supabaseRows
argument, set the “Value” to “Widget Builder Parameters” -> rows
.Still under “Component Parameters” add the SQL query to fetch all list items from the SQLite database:
select * from lists order by created_at;
Still under “Component Parameters”, check “watch [Boolean]”. This ensures that the query auto-updates.
lists
table is empty in Supabase. Create a test row in the table by clicking on “Insert” -> “Insert Row” in your Supabase Table Editor.
id
and created_at
blank.owner_id
and select your test user.Checkpoint: You should now see your single test row magically appear in your app:
In this section, we will be making the ListView
component clickable and navigate the user to a page which will eventually display the list’s To-Do items. This page will show the selected list’s name in the title bar (“AppBar”). This uses Page State and the PowerSyncStateUpdater
library component.
This parameter will store the selected list’s ID.
Todos
.Todos
page.id
.This variable will store the selected list row.
Todos
page selected:list
.lists
.Under the “Widget Palette”, select the “Components and custom widgets imported from library projects” panel.
Drag the PowerSyncStateUpdater
library component into your page.
Under the “Widget Tree”, select the PowerSyncStateUpdater
component.
In the “Properties” panel on the right, under “Component Parameters”:
select * from lists where id = :id;
id
.id
.Still under “Component Parameters”, configure the “onData” action:
list
page state variable.supabaseRowsToList
.rows
Still under the “Widget Tree”, select the “AppBar” -> “Text” widget.
List Name
.ListView
Component ClickableListItems
component.ListTile
widget.Todos
page.Instant Reload your app or start another test session.
Checkpoint: You should now be able to click on a list item and it should navigate you to a new page showing the name of the list in the title bar:
This section is a work in progress. Please reach out on our Discord if you have any questions.
You will now update the app so that we can capture new list entries.
HomePage
page.INSERT INTO lists(id, created_at, name, owner_id) VALUES(uuid(), datetime(), 'new item', :userId);
userId
parameter we’re using the above query:
userId
.Checkpoint: Reload your app and click on the + floating action button. A new list item should appear, which also automatically syncs to Supabase:
Updating data is possible today using the powersyncWrite
helper of the Library, and a guide will be published soon. In the mean time, use the section below about Deleting Data as a reference. Please reach out on our Discord if you have any questions.
In this section we will add the ability to swipe on a ListTile
to delete it.
Under the “Page Selector”, select your ListItems
component.
Under the “Widget Tree”, select the ListTile
widget.
In the “Properties” panel on the right, enable “Slidable”.
Click “Open Slidable”.
Select the “SlidableActionWidget”.
In the “Actions” panel on the right, click “Add Action”.
delete from lists where id = :id;
id
parameter we’re using the above query:
id
.Checkpoint: Reload your app and swipe on a list item. Delete it, and note how it is deleted from the list as well as from Supabase.
signOut
without Arguments or Return Values and paste the below code:In the below code, power_sync_b0w5r9
is the project ID of the PowerSync library. Update it if it changes.
Click “Save Action”.
Under the “Page Selector”, select your HomePage
page.
Under the “Widget Palette”, drag a “Button” onto the right of your “AppBar”.
In the “Properties” panel on the right, rename the “Button Text” to Sign Out
.
Switch to the “Actions” panel and open the “Action Flow Editor”.
Select “On Tap” as the action trigger.
Click “Add Action” and add a call to the signOut
Custom Action.
Chain another Action and call to “Supabase Authentication” -> “Log Out”:
Checkpoint: You should now be able to reload your app and sign out and in again.
The PowerSync library provides a built-in component that displays real-time connectivity and synchronization status. Since the sync state is available globally as part of your app state, you can easily monitor the database status throughout your application. To add this status indicator:
Under the Widget Palette, select the “Components and custom widgets imported from library projects” panel.
Drag the PowerSyncConnectivity
component into your home page’s “AppBar”.
PowerSync’s Sync Rules and Supabase’s support for Row Level Security (RLS) can be used in conjunction. Here are some high level similarities and differences:
Run the below in your Supabase console to ensure that only list owners can perform actions on the lists table where owner_id
matches their user id:
Currently all lists are synced to all users, regardless of who the owner of the list is. You will now update this so that only a user’s lists are synced to their device:
sync-rules.yaml
file.Checkpoint: Your app should continue running seamlessly as before.
For column values, PowerSync supports three basic types: Integers, doubles, and strings. These types have been chosen because they’re natively supported by SQLite while also being easy to transport as JSON.
Of course, you may want to to store other values in your Postgres database as well. When syncing a value that doesn’t fit into the three fundamental types, PowerSync will encode it as a JSON string. To use those values in your app, you’ll need to apply a mapping so that you display the correct values and use the correct representation when uploading data.
As an example, let’s consider an added tags
column on the lists
table used in this guide. These tags will be
encoded as a string array in Postgres:
Like all array values, PowerSync will transport this as a JSON string. For instance, a row with the default tags would
be represented as this string: ["default", "tags"]
.
FlutterFlow does not support extracting a list from that string, so the custom functions responsible
for mapping SQLite rows to FlutterFlow classes needs to be aware of the transformation and reverse it:
This transforms the '["default", "tags"]'
value as it appears into ["default", "tags"]
, the list value expected
for this row.
A similar approach is necessary when making local writes. The local database should be consistent with the data synced with PowerSync. So all local writes should write array and JSON values as strings by encoding them as JSON.
Finally, the PowerSync mapping also needs to be reverted when uploading rows to Postgres. For a
text[]
column for instance, the local string value would not be accepted by Supabase.
For this reason, the upload behavior for columns with advanced types needs to be customized.
New feature: This option has been added in version 0.0.7
of the PowerSync FlutterFlow library.
Please make sure you’re using that version or later.
To customize the uploading behavior, create a new custom action (e.g. applyPowerSyncOptions
). After the
default imports, put this snippet:
Also, add this function to your main.dart
as a final action.
When setting powersyncOptions.transformData
, a callback is invoked every time a created or updated row
is uploaded to Supabase.
This allows you to customize how individual values are represented for Postgres. In this case, the tags
column of the lists
table is decoded as JSON so that it’s uploaded as a proper array while being stored
as a list locally.
To enable an easy setup, the PowerSync FlutterFlow library integrates with Supabase by default. This means that as long as you use Supabase for authentication in your app, PowerSync will automatically connect as soon as users log in, and can automatically upload local writes to a Supabase database.
For apps that don’t use Supabase, you can disable this default behavior and instead rely on your own
backend connectors.
For this, create your own custom action (e.g. applyPowerSyncOptions
). It’s important that this action runs
before anything else in your app uses PowerSync, so add this action to your main.dart
as a final action.
For more information on writing backend connectors, see integrating with your backend.
Below is a list of known issues and limitations.
Podfile
located in the ios/
directory.Podfile
needs to be updated from use_frameworks! :linkage => :static
to use_frameworks!
(remove everything after the exclamation sign).