I need to make an app. I’m an experienced programmer. These are my 2023 notes.
Getting Started
I choose Flutter to (hopefully) have a single code base for Andriod, iOS and (maybe) Web. Inital impressions are good.
First thing is to get Hello World running, so I need a dev environment. I followed resources at https://flutter.dev/learn.
Environment
I tried a few:
- Windows : actually ok to install and run a Hello World. Uses Android Studio and VSCode. When I got into it, using Firebase and secrets, I found it harder (e.g. how to store secrets? how to run firebase cli?). I decided to change as my main env is linux.
- HyperV on Windows running Ubuntu: my main environment. Would be great but I got stuck: I couldn’t run an Andriod Emulator (e.g. Pixel VM) in Ubuntu in Windows. To do with KVM hardward acceleration and nested VMs. Seems not possible. Also, the Ubuntu VM can’t easily see USB ports for testing on a phone.
- Ubuntu baremetal on a Dell laptop. Seems the way to go. Installing software (vscode, flutterfire, dart) is way easier than Windows. Shortcuts from Ubuntu interfere with VSCode (fix ’em via
gsetting
s ).
Learning it
- Flutter Docs are really good
- “Flutter Complete Reference” by Alberto is a good reference.
- I’m starting with online tutorials (see “Progress”)
- Flutter Channel on YouTube is excellent for understanding. The “Flutter Complete Reference” confused me about async/await, but this channel had good explainations.
Design Choices
Based on current (late 2023) best practice, I chose:
- riverpod : state management. Provider successor.
- material: obvious UI choice
- json_serialiable: auto-generator for json parsing, used by riverpod examples
- freeze: auto-generator for data classes, used by riverpod examples
My Use Case
Easy enough to get VSCode running a hello world running on both emulator and Andriod. Just follow online docs.
The initial idea was to use WebView to display an existing website based on Grafana. However, it is difficult or impossible to do this because of authentication. Plus it seems better longterm to have a stand-alone native (or Darty) app.
My key functionality requirements of the app are
- Authentication with Google OAuth and maybe Apple and email/password
- Consume an existing RESTful endpoint
- Draw graphs
- Notifications
Progress
Beginnings
Code Lab “Get to Know Flutter”: Good instructions to get something running. Didn’t complete as FireBase centric.
- Ok until doing Google Auth. To add google auth sign for a Android app:
- Enable Google Auth as a sign-in method in FireBase console
- Get the SHA1 of the app and add to FireBase.
- In FIreBase Console, use “SDK Instructions” and download google-services.json in the right place.
- Still have to copy “Web client id” in auth-gate.dart
- Now we can sign in with Google!
- This authenticates the user to the app. That is, the app knows who you are. THere is no permission/authorisation yet.
1 Authentication
Architecture
Users are stored in a Django/PostgresSQL database on a server. There is an email and password for each user. Each user can manage devices. We use an REST API (via DRF including authentication ) to expose endpoints.
To use the endpoint, TokenAuthentication is used. Each Django user has a Token which is sent with each api request.
The app needs a copy of this token to make calls (to grab user’s / device’s data). If the app user enters their email (aka username) and password, the app can a pre-rolled DRF endpoint to get a token. Great! Todo: how to let the app user enter their details ONCE and persist them? That is, on first run we get email/password and on second run we use that without asking the user again.
Implementation
Email / password
We’ll make a test in Django to create a user and call the obtain-auth-token endpoint of DRF. A gotcha is that User.password is the hash of the password. So you should use User.set_password(raw)
and User.check_password(raw)
to validate. To do with FactoryBoy checkout this. So the test looks like this:
@pytest.mark.django_db
def test_get_token(api_client: APIClient, devices_fixture):
u = PhiUser.objects.get(email="u1@test.com")
# Check 'manually' via Django
test_login = authenticate(
request=None, username=u.get_username(), password="abc"
)
data = {"username": u.get_username, "password": u.password}
# Run against the endpoint
response = api_client.post(reverse("phisaverweb:get-token"), data)
print(response)
We can do a direct HTTP call to double check before we look at the app:
curl -X POST localhost:8000/phisaver/api/v1/token/
-d '{"username":"brettbeeson@fastmail.com","password":"XXXXXX"}'
-H 'Content-Type: application/json'
Ok, so we have a token maker using the username/password via the API. Let’s modify the app to call this endpoint instead of using FireBase to store users.
App Signin
FireBase auth is good to get started, but oh no, can’t do this: I tried to override EmailAuthProvider but it’s tightly linked to Firebase. I don’t really need Firebase (the API will store users). I can’t fudge it because FireBase.User() is a private constructor. One option would be to repeat (DRY?) users in FireBase and the API. That’s annoying, but would show app users in Firebase console. This breaking of DRY might be worth it?
Instead, we could skip FireBase. Seems FireBase is THE way to do it, so this is a bit risky. For example, the “Complete Flutter Reference” only talks about user auth with FireBase.
Some starting points for login apps/widgets/libaries are below.
- flutter_login: can do email and Social. Popular. Worth a go.
- This is pretty good and easy to use
- Unfortunately, using RiverPod providers didn’t mesh well. Specially, I couldn’t return a String from loginCallback because NotifierProvider was giving an ASyncValue. Tried really hard! But giving up.
- google_sign_in: just for Google I think
- basic: simple base code with no dependencies.
- After failure of flutter_login + RiverPod, I just used this. It was very straightforward to use riverpod
- Flutter Apprentice: this book has a nice run-through of a simple login
Outh2 and Social Providers
This is tricky. The “FireBase Auth” tutorial above managed to authenticate with Google. Then we can use
var oAuth2IDToken = await FirebaseAuth.instance.currentUser!.getIdToken();
However, using google api at https://www.googleapis.com/oauth2/v[1-3]/tokeninfo?[id|access]_token=XXXX says invalid token. I can decode it from base64 and see it’s a JWT. I used python to do this.
I think this is a short-lived token we can pass to a Django DRF endpoint. This endpoint can read the token, verify it and pass back a ‘standard’ DRF token which the app will include in all requests.
So we need a python something to accept the OAuth2 token and verify it. An article on library choices for DRF. Seems like dj-rest-auth is most suitable.
2 JSON RESTful Endpoints
Dart’s HTTP and JSON libraries, plus auto-gen code for RESTful APIs are easy. Maybe a Django->Dart code generator would be worthwhile.
@json_serializable auto-generates code. After setup, it’s good. @freeze
Random Snippets
There are bit I found tricky
Code Generator or Manual?
Maybe use https://pub.dev/packages/swagger_dart_code_generator to generate class from API, or maybe
Or simple Django->Dart? https://github.com/deuse-sprl/djangomodel2dart. This works ok and is basic to use. It just regex converts Django (python classes) to Dart Classes.
This is great: https://app.quicktype.io/. Just give it JSON sample and bang!, code to go.
Formal initialisers, constructors
class Device {
# Required normal positional
Device(name,ref) { ... set up manually ... }
# Adding "this." auto-init ("initialising formal")
# Required, auto-init
Device(this.name,this.ref);
# Named parameters use "{ }"
# Named parameters *default to null* and *default to optional*
# Optional, named, auto-init
Device({this.name, this.ref});
# Required, named, auto-init
Device({required this.name, required this.ref});
}
Secrets in Dart and Flutter
I like .env files and use them for such things as Django Python development. But a Flutter app’s code is exposed on the user’s device. Don’t use .env files naively, but read this article and probably use envied.