Download a latest release of SFRF
Unzip, cd into it, for later use.
It is a good idea to start from a skeleton app that did heavy lifting for you.
git clone https://github.com/01kg/flutter_skeleton_application_improved.gitThis skeleton flutter app is based on the "Flutter Skeleton Application" template. Added support for Freezed, Riverpod, Supabase, DotEnv.
With 4 essential views:
- Home
- Settings
- Login
- Signup
cd flutter_skeleton_application_improved
# install packages
dart pub getcp .env.example .env
cat .env
# Output:
# SUPABASE_URL=https://YOUR.SUPABASE.URL
# SUPABASE_ANON_KEY=YOUR_ANON_KEYReplace the credentials with your own Supabase's.
flutter runSignup/Login a Supabase user account, change to dark theme in settings to make sure the app runs good.
If the skeleton app runs good, then let's generate files based on SQL CREATE statement.
Recommend using DBDiagram to generate Postgres code.
create table countries (
id bigint generated by default as identity primary key,
name varchar,
description varchar,
user_id uuid references auth.users on delete cascade on update cascade
);
create table cities (
id bigint generated by default as identity primary key,
name varchar,
description varchar,
population bigint,
established_date date,
area real,
introduction text,
longitude double precision,
lantitude double precision,
country_id bigint references countries on delete cascade on update cascade,
user_id uuid references auth.users on delete cascade on update cascade
);
-- If you want to turn on RLS to restrict access:
alter table "countries" enable row level security;
create policy "Authenticated can view own country."
on countries for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "Authenticated can create own country."
on countries for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Authenticated can update own country."
on countries for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
create policy "Authenticated can delete own country."
on countries for delete
to authenticated
using ( (select auth.uid()) = user_id );
alter table "cities" enable row level security;
create policy "Authenticated can view own city."
on cities for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "Authenticated can create own city."
on cities for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Authenticated can update own city."
on cities for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
create policy "Authenticated can delete own city."
on cities for delete
to authenticated
using ( (select auth.uid()) = user_id );We created two tables, countries and cities. Here are something worth noting
(some best practices):
-
Every table should have
idcolumn as the Primary Key.- Essential for being referenced.
- Essential for "Delete" operation.
- Use
bigintto be more readable. - use
generated by defaultto let database decide how to create it. (generated alwayswould block UPSERT operations) - use
as identityto make it auto increment. - use
primary keymake it able to add relationships with other tables
-
user_idcolumns is good for RLS access policy doing restrictions.references auth.users on delete cascade on update cascadeis a good practice. -
cities table has a field refers to countries. The field name should be
country_id, notcountry. Because in database, this field is acctually the id of a row of countries table. SFRF uses_idto identify these fields and create a related query and a dropdown list. -
Use
varcharfor short content text. E.g.name,description. This is good for indexing. SFRF would create a one-line height TextFormField for it. -
Use
textfor reaaaaaaaaally long text. E.g.notes,post_content. SFRF would create a 2-8 lines height TextFormField for it. -
Use
datefor date. SFRF would create a date picker for it. -
Use
realfor 6 digits float, and usedoubleprecision for 15 digits float, usenumericfor n digits float. -
Use snake_case for multi-words field name. E.g.
country_id. SFRF has built-in methods to convert to camelCase, CapCamelCase, Title Case.
The SQL statment script above has two usage:
- Input it in Supabase's SQL Editor to create tables and policies.
- SFRF generates all files based on it.
- Reads
lib/sqlsfolder for.sqlfiles. - Parse
create table ... ;part to get all the fields (table columns) and types - Go to/Create
lib/modelsfolder, put generated Freezed annotated files in it. - Go to/Create
lib/providersfolder, put generated Riverpod annotated files in it. - Go to/Create
lib/viewsfolder, put generated view files in it.
Copy and paste the SQL statements to in it and run. Make sure no error returned.
You can go to Database -> Schema Visualizer to check the tables' relationships.
Step 3: Create a .sql file with any name in sqls folder, and paste the SQL statements content into it.
Thus, all files are generated.
This command let Freezed and Riverpod to generate their own codes.
If encounter warning:
Found 4 declared outputs which already exist on disk.
This is likely because the`.dart_tool/build` folder was
deleted, or you are submitting generated files to your
source repository.
Delete these files?
1 - Delete
2 - Cancel build
3 - List conflicts
Unless you have deep concerns about this, or just select 1 to delete these old files.
The generated files might not match Dart's format standard, it is recommend to format them for better experience for later editing.
Since by the time of writing, dart format
doesn't recurse through subdirectories,
and does not recognize common seen auto-generated files like '*.g.dart' or
'*.freezed.dart', so it is necessary to write a command to do so.
# Windows PowerShell
Get-ChildItem -Path .\lib -Recurse -Filter *.dart | Where-Object { $_.Name -notlike '*.g.dart' -and $_.Name -notlike '*.freezed.dart' } | ForEach-Object { dart format $_.FullName }# Unix/Linux/macOS
find ./lib -name "*.dart" ! -name "*.g.dart" ! -name "*.freezed.dart" -exec flutter format {} \;Add new views to onGeneratedRoute configuration:
// app.dart
...
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute<void>(
settings: routeSettings,
builder: (BuildContext context) {
final user = ref.watch(supabaseAuthProvider);
if (user == null) {
// if (authEvent.value?.event != AuthChangeEvent.signedIn) {
if (routeSettings.name == SignupView.routeName) {
return const SignupView();
}
return const LoginView();
} else {
switch (routeSettings.name) {
case SettingsView.routeName:
return const SettingsView();
case SignupView.routeName:
return const SignupView();
case CountriesView.routeName:
return const CountriesView();
case CitiesView.routeName:
return const CitiesView();
default:
return const HomeView();
}
}
},
);
},
...Link views to Settings page:
// views/settings_view.dart
...
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButton<ThemeMode>(
...
),
const Divider(),
ListTile(
title: const Text('Countries'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
// go to investment projects view
Navigator.restorablePushNamed(context, CountriesView.routeName);
},
),
const Divider(),
ListTile(
title: const Text('Cities'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
// go to investment projects view
Navigator.restorablePushNamed(context, CitiesView.routeName);
},
),
],
),
),
...After SFRF did heavy lifting things, it your turn, modify as you like.
At least there are 2 places for you to do something:


