FlutterFlow + PowerSync
Integration guide for creating offline-first apps with FlutterFlow and PowerSync with Supabase as the backend.
This is our new and improved guide that only requires using Custom Actions to integrate with PowerSync. Using GitHub is not required.
The guide takes you through building a basic app from scratch. The app lets you manage a list of items. You should then be able to use this knowledge to build/extend your own app.
Used in conjunction with FlutterFlow, PowerSync enables developers to build offline-first apps that are robust in poor network conditions and that have highly responsive frontends while relying on Supabase for their backend. This guide provides instructions for how to configure PowerSync for use with your FlutterFlow project that has Supabase integration enabled.
This guide takes 30-40 minutes to complete.
Before you proceed, this guide assumes that you have already signed up for free accounts with both Supabase and PowerSync. If you haven't signed up for a PowerSync account yet, click here (and if you haven't signed up for Supabase yet, click here). This guide also assumes that you already have Flutter set up.
This guide also requires FlutterFlow Local Run, so be sure to download and install that.
Guide Overview
Configure Supabase and PowerSync prerequisites
Initialize your FlutterFlow project
Build a sign-in screen
Initialize PowerSync
Reading data
Creating data
Deleting data
Signing out
Securing your app
Enable RLS in Supabase
Update Sync Rules in PowerSync
Configure Supabase
PowerSync uses the Postgres Write Ahead Log (WAL) to replicate data changes in order to keep PowerSync SDK clients up to date.
Run the below SQL statement in your Supabase SQL Editor:
Create a Postgres publication. This will enable data to be replicated from Supabase so that your FlutterFlow app can download it.
Note: this guide uses the default postgres
user in your Supabase account for replicating changes to PowerSync, since elevating custom roles to replication has been disabled in Supabase. If you want to use a custom role for this purpose, contact the Supabase support team.
Note: this is a static list of tables. If you add additional tables to your schema, they must also be added to this publication
Configure PowerSync
Connect PowerSync to Your Supabase
In the PowerSync dashboard Project tree, click on "Create new instance":
Give your instance a name, such as "Supabase Testing".
Under the "General" tab, you can change the default cloud region from US to EU or JP if desired (more cloud regions are available, contact us if you need a different region).
Under the "DB Connections" tab, click on the + icon.
Now we get the connection details from Supabase:
In your Supabase dashboard, navigate to "Project Settings" -> "Database" -> "Connection string" and select the "URI" tab.
Uncheck the "Use connection pooling" checkbox. PowerSync needs to connect to the database directly and cannot use the pooler.
Copy the connection string. The hostname should be
db.<PROJECT-ID>.supabase.co
, and not, for example,aws-0-us-west-1.pooler.supabase.com
.Paste this URI in PowerSync the instance URI field.
Enter the password for the
postgres
user in your Supabase database: (Supabase also refers to this password as the database password or project password).PowerSync has the Supabase CA certificate pre-configured —
verify-full
SSL mode can be used directly, without any custom certificates.
Click "Test Connection" and fix any errors.
Under the "Client Auth" tab, enable "Use Supabase Auth".
Click "Save".
PowerSync deploys and configures an isolated cloud environment for you, which will take a few minutes to complete.
Configure Sync Rules
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 to-do lists and list items.
1. To update your Sync Rules, open the sync-rules.yaml
file.
Replace the
sync-rules.yaml
file's contents with the below:
For additional information on PowerSync's Sync Rules, refer to the Sync Rules documentation.
If you're wondering how Sync Rules relate to Supabase Postgres RLS, see this subsection.
Initialize Your FlutterFlow Project
Create a new Blank app, give it a name, and disable Firebase
Under "App Settings" -> "Integrations", enable Supabase. Enter your "API URL" and "Anon Key" and click "Get Schema"
Under "App Values" -> "Constants", click "Add App Constant"
For Constant Name, enter
PowerSyncUrl
For Constant Value, follow these steps:
Click "Edit instance"
Hover over "Instance URL" to copy the value:
You should now see this under App Constants:
Build A Sign-In Screen
Under Pages, click "Add Page, Component or Flow"
Select the first template and name the page "Login"
Under "App Settings" -> "App Settings" -> "Authentication":
Enable Authentication
Set Supabase as the Authentication Type
Set the Login page you just created as the Entry Page
In your Supabase Dashboard, under "Authentication", click on "Add User" -> "Create new user" and create a user for yourself to test with
Launch your app on a physical or simulator device
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.
Initialize PowerSync
Click on "Custom Actions" -> "Add" -> "Action"
Name the Custom Action
initpowersync
NOTE: use all lowercase for this Custom Action is important due to naming conversion that FF performs behind the scenes
Copy and paste the custom action code from here: https://github.com/powersync-ja/powersync-flutterflow-template/blob/flutterflow/lib/custom_code/actions/initpowersync.dart
Import your schema
Paste this into your Custom Action code on line 27 after the equals sign
Due to a limitation in FF, you now need to prefix each instance of
Schema
,Column
andTable
withpowersync
Your custom action schema definition should now look like this:
Under "Action Settings" on the right, add this dependency into "Pubspec Dependencies":
powersync: ^1.3.1
Still in Custom Actions, under "Custom Files" click on
main.dart
and set your new Custom Action as a Final Action and click Save.
Checkpoint: You should now be able to validate that PowerSync is initializing correctly by taking these steps:
Stop any running simulator app sessions
Restart the app by clicking "Test"
Click on "Open Device Logs"
You should see this kind of log message:
Reading Data
We will now create our first UI and bind it to the data in the local SQLite database on the device.
Create a Custom Action to Stream all Lists
For watched (realtime) queries in FlutterFlow, you need 2x Custom Actions per table. For delete, update and insert queries you only need 1x Custom Action. We are working to see if we can aleviate this constraint.
Create a new Custom Action and call it
watchLists
and paste the below code:
Your Action Arguments should now look as follows:
Click Save Action
Create the second Custom Action called
getLists
and paste the following code into it:
Your Action Arguments should now look as follows:
On the HomePage page, you will create a placeholder Page State variable required for the next step.
Click on State Management
Still on the HomePage page, select Actions and click "Open Action Editor"
Add the
watchLists
Custom ActionClick "Open" to edit the
callback
Argument forwatchLists
Add the
getLists
Custom Action and set theresults
Action Argument toresult
and click "Confirm"Set the "Action Output Variable Name" to
allLists
and you should now see this:Add a second action to the chain, and set it to "Update Page State" and "Rebuild Current Page". This is to ensure the page gets redrawn when the database updates. Your callback action should now look like this:
Click "Close" to exit the action editor.
In the UI Builder on the HomePage page, add a ListView component and add a ListTile inside the ListView
On the ListView component, click "Generate Dynamic Children". Enter a variable name of
boundLists
and set its value toallLists
(no further changes)On the ListTile component, set the Title field to "Set from Variable" and then get the
name
field from theboundLists
variable
Do the same for the Subtitle field of the ListTile component, and set it to
created_at
Hot reload your app and the screen will still be blank. This is because the
lists
table is empty in Supabase. Create a test row in the table by clicking on "Insert" -> "Insert Row" in your Supabase Table EditorLeave
id
andcreated_at
blankEnter a name such as "Test from Supabase"
Click "Select Record" for
owner_id
and select your test user
Checkpoint: you should now see your single test row magically appear in your app
Creating Data
You will now update the app so that we can capture new entries.
Create a new Custom Action called
createListItem
and paste the following code:
There should now be one argument for the Custom Action called
name
of type String and not nullable.In the Widget Tree view, select the HomePage page and navigate to State Management
Create a new State Field called
fabClicked
and set the type to and toggle the "Initial Field Value" toggle twice to initialize the field to false.In the Widget Tree view, drop in a Floating Action Button component. Your Page State should now look like this:
From the Widget Palette view, drop a Floating Action Button (FAB) onto the page.
Click on the FAB and Open the Action Flow Editor
Add an action to Update Page State
On the Widget Palette again, add a Container child to the Column Widget
Now add a Column Widget to this Container
Add a TextField and a Button to this Column Widget
Set the Container and TextField widgets to have a width of 100%
Change the Button text to "Add"
Open the Action Flow Editor for the Add button
Add a Custom Action call to
createListItem
Set the "name" Argument to Widget State -> TextField
Chain another Action of "Clear Text Fields / PIN Codes" to clear the TextField field
Chain another Action to Update Page State and set
fabClicked
to falseYour Action Editor should now look like this:
Checkpoint: you should now be able hot reload your app, click on the FAB button and the TextField should appear. Enter a name and click Add. The new row should appear in the ListView and the TextField should be hidden again.
Deleting Data
In this section we will add the ability to swipe on a ListTile to delete it.
Create a new Custom Action called
deleteListItem
and paste the below code:
In the Widget Tree select the ListTile and enable Slidable
Checkpoint: Stop and relaunch the app (Hot Reload won't work after adding slidable) and you should be able to swipe on items to delete them. Note that they are also magically deleted from Supabase!
Signing Out
Create a new Custom Action called
signOut
without Arguments or Return Values and paste the below code:
Click Save Action
In the Widget Tree, drag a Button onto the right of your App Bar
Rename the button text to "Sign Out"
Open Action Editor and click Open to launch the editor
Add a call to the
signOut
Custom ActionChain another call to Auth -> Log Out:
Click Close
Checkpoint: You should now be able to hot reload your app and sign out and in again.
Securing Your App
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:
RLS should be used as the authoritative set of security rules applied to your users' CRUD operations that reach Postgres.
Sync Rules are only applied for data that is to be downloaded to clients — they do not apply to uploaded data.
Sync Rules can typically be considered to be complementary to RLS, and will generally mirror your RLS setup.
Enable RLS in Supabase
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:
Update Sync Rules
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:
Navigate to the PowerSync dashboard and open your sync-rules.yaml file
Delete the existing content and paste the below contents:
Click on "Validate"
Click on "Deploy sync rules"
Wait for the deploy to complete
Checkpoint: Your app should continue running seamlessly as before.
Known Issues, Limitations and Gotchas
Below is a list of known issues and limitations
It's not currently possible to use the FlutterFlow Web Editor to test your app due to limitations with FlutterFlow.
When trying to compile any of the PowerSync Custom Actions, you will see errors — these can be safely ignored:
Using
watch()
queries creates a StreamSubscription and it's important to regularly call.cancel()
on these to avoid multiple subscriptions for the same query running.
Video Tutorial
Last updated