FlutterFlow + PowerSync
Integration guide for creating offline-first apps with FlutterFlow and PowerSync with Supabase as the backend.
Video walkthrough of the integration guide.
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.
Guide Overview
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.
This guide takes 30-40 minutes to complete.
- 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
- Create a new project in 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 using the SQL Editor. 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
Create a PowerSync Cloud Instance
- In the PowerSync Dashboard Project tree, click on “Create new instance”:
- Give your instance a name, such as “Testing”.
- Under the “General” tab, you can change the default cloud region from US to EU, JP (Japan), or AU (Australia) if desired.
- Note: More cloud regions are available, contact us if you need a different region.
- Under the “DB Connections” tab, click on the + icon and select your database technology.
Connect PowerSync to Your Supabase
-
In your Supabase Dashboard, click Connect in the top bar:
- Under Direct connection, 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 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.
- Under Direct connection, copy the connection string. The hostname should be
-
Click Test Connection and fix any errors.
-
Under the Client Auth tab, enable Use Supabase Auth and enter your Supabase JWT Secret:
PowerSync uses the secret to verify Supabase's JWTs
- Click Save and deploy to deploy the updates to your instance.
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, copy and paste your instance URL from the PowerSync Dashboard:
- For Constant Name, enter
You should now see this under App Constants:
Build A Sign-In Screen
- Under Pages, click “Add Page, Component or Flow”.
- Select the Auth1 template and name the page “Login”.
- Delete the Sign Up, Forgot Password and Social Login buttons — we will only be supporting Login for this demo app.
- 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.
- Set HomePage as the Logged In 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:
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:
Initialize PowerSync
- Click on “Custom Code” -> “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:
- On the PowerSync Dashboard, right-click on your instance and select “Generate Client-Side Schema” and select Dart as the language.
- 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.8.4
- FlutterFlow imports and old version of sqflite by default and it’s not possible to remove it, so you also need to add this dependency:
sqflite: ^2.3.3
- Your dependencies should now look as follows:
- FlutterFlow imports and old version of sqflite by default and it’s not possible to remove it, so you also need to add this dependency:
- Save your new custom action
- 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”, and sign in
- 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:
- Hit Save and click “Yes” on the popup to set the Action Arguments for you:
- Your Action Arguments should now look as follows:
- Create the second Custom Action called
getLists
and paste the following code into it:
- Hit Save and click “Yes” on the popup to set the Action Arguments for you:
- 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.
- Add a dummy variable called “notused” or similar and click Confirm:
- Still on the HomePage page, select Actions and open “Action Flow Editor”.
- Add the
watchLists
Custom Action. - Click “Open” to edit the
callback
Argument forwatchLists
. - Add the
getLists
Custom Action and set theresults
Action Argument toresult
and click “Confirm”:
- Add the
- 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 Flow 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). Click Save. - 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 Editor. - Leave
id
andcreated_at
blank. - Enter 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 list entries.
- Create a new Custom Action called
createListItem
and paste the following code:
- Hit Save and click “Yes” on the popup to set the Action Arguments for you:
- 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 boolean and toggle the “Initial Field Value” toggle twice to initialize the field to false. -
- In the Widget Tree 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.
- Set the
fabClicked
value totrue
and click Close.
- 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.
- Your homepage layout should now look like this:
- Set the Container and TextField widgets to have a width of 100%.
- Click on the Container and enable Conditional Visibility for
fabClicked
.
- 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 1.
- Chain another Action of “Clear Text Fields / PIN Codes” to clear the TextField_1 field.
- Chain another Action to Update Page State and set
fabClicked
to false. - Your 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:
- Hit Save and click “Yes” on the popup to set the Action Arguments for you:
- The Custom Action Arguments should now look as follows:
- In the Widget Tree select the ListTile and enable Slidable.
- Select the SlidableActionWidget from the Widget Tree and set the values to the following:
- Click on Action Editor and click Add Action, passing in
boundLists
todeleteListItem
as follows:
Checkpoint: Stop and relaunch the app (Hot Reload won’t work after adding the slidable package) and you should be able to swipe on items to delete them. Note that they are also magically deleted from Supabase!
Updating Data
In this section we will add the ability to update a list item. It entails:
- A custom action to handle updating the data
- Setting and using state fields to show/hide UI dynamically and reference the list item to edit
- A button to edit a list item (set up similar to the Delete button in the previous section)
- UI to enter and save the new item name (set up similar to the Create functionality we covered earlier)
- Create a new Custom Action called
updateListItem
and paste the below code:
- Hit Save and click “Yes” on the popup to set the Action Arguments for you:
- The Custom Action Arguments should now look as follows:
- In the Widget Tree view, select the HomePage page and navigate to State Management.
- Create a new State Field called
editClicked
, set the type to Boolean and toggle the “Initial Field Value” toggle twice to initialize the field to false. - Create another new State Field called
listItemIndex
, set the type to Integer. Click Confirm.
- In the Widget Tree select the ListTile.
- Under Slidable Properties click Add Action.
- Select the new SlidableActionWidget from the Widget Tree and set its properties to the following:
- Open the Action Flow Editor.
- Add an action to Update Page State.
- Add Field: Set the
editClicked
value totrue
. - Add Field: Set the value of
listItemIndex
to the “Index in List” of theboundLists
Item and click Close.
- Chain another Action to “Set Form Field” -> TextField_2. This will initialize the text field to the current list item’s name.
- Set the variable to the boundLists Item
- Under Available Options, select “Get Row Field”
- Under Supabase Row Field, select “name”
- Your action should look like this:
- 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.
- Your homepage layout should now look like this:
- Set the Container and TextField widgets to have a width of 100%.
- Click on the Container and enable Conditional Visibility for
editClicked
.
- Change the Button text to “Save”
- Open the Action Flow Editor for the Save button.
- Add a Custom Action call to
updateListItem
. - Set the “name” Argument to Widget State -> TextField 2.
- Set the “row” Argument:
- Select Action Outputs -> allLists.
- Under Available Options select “Item at Index”.
- Under List Index Options select “Specific Index”.
- Set the Index value to the
listItemIndex
Page State variable
- Add a Custom Action call to
- Click Confirm
- Chain another Action of “Clear Text Fields / PIN Codes” to clear the TextField_2 field.
- Chain another Action to “Update Page State”.
- Add Field: Set
editClicked
to false. - Add Field: Set
listItemIndex
to Reset Value. - Your Action Editor should now look like this:
- Close the Action Flow Editor.
Checkpoint: you should now be able hot reload your app, slide on an item to edit it. Enter the new item name into the text field that appears, and hit Save. The update should then reflect in 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 Action. - Chain 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. - Deploying to the Apple App Store currently requires some workarounds due to limitations in FlutterFlow:
- Download the code from FlutterFlow
- Open the
Podfile
located in theios/
directory - The following option in the
Podfile
needs to be updated fromuse_frameworks! :linkage => :static
touse_frameworks!
(remove everything after the exclamation sign) - After removing that option, clean the build folder and build the project again.
- You should now be able to submit to the App Store
- Exporting the code from FlutterFlow using the “Download Code” action in FlutterFlow requires the same workaround listed in 4. above.
- Other common issues and troubleshooting techniques are documented here: Troubleshooting
Was this page helpful?