A Flutter application for the PLC community.
- Flutter SDK 3.35.5 or higher
- Dart SDK 3.6.2 or higher
- Android Studio / Xcode (for mobile development)
- Google Sheets API service account credentials
git clone <repository-url>
cd plcflutter pub getThe app uses envied for secure, obfuscated environment variables. You have several options to set this up:
Run the interactive setup script:
./setup_credentials.shThe script will guide you through:
- Importing from an existing
service_account.jsonfile - Pasting the JSON content directly
- Creating from template for manual editing
-
Copy the template file:
cp .env.template .env
-
Edit
.envand replace the placeholder JSON with your actual Google service account credentials -
Save the file
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google Sheets API
- Go to "IAM & Admin" > "Service Accounts"
- Create a new service account
- Generate and download the JSON key
- Share your Google Sheets with the service account email address
After creating your .env file, generate the obfuscated environment code:
dart run build_runner build --delete-conflicting-outputsThis creates lib/core/config/env.g.dart with your encrypted credentials.
flutter runDebug Build:
flutter build apk --debugRelease Build:
flutter build apk --releaseAndroid App Bundle (for Play Store):
flutter build appbundle --releaseflutter testlib/
├── core/
│ ├── features/ # Generic features library
│ │ ├── data/
│ │ │ ├── datasources/ # Generic data source implementations
│ │ │ ├── models/ # Base model interfaces
│ │ │ └── repositories/ # Generic repository implementations
│ │ ├── domain/
│ │ │ ├── repositories/ # Repository interfaces
│ │ │ └── usecases/ # Generic use cases
│ │ ├── presentation/
│ │ │ └── bloc/ # Generic BLoC components
│ │ ├── core_features.dart # Library exports
│ │ └── USAGE_EXAMPLE.md # Detailed usage guide
│ └── storage/
│ ├── gsheets_storage_service.dart # Google Sheets integration
│ └── local_storage_service.dart # Local caching
├── features/ # Feature modules
│ ├── preachers/ # Preachers feature (old pattern)
│ └── secretary/ # Secretary feature (new pattern)
└── main.dart # App entry point
This project includes a powerful Generic Features Library (lib/core/features/) that drastically reduces boilerplate when creating new read-only features.
- ~70% less code per feature
- Automatic caching with 1-day sync by default
- Built-in search and filter support
- Type-safe generic implementations
- Google Sheets integration out of the box
Creating a new feature only requires:
- Define Entity (domain/entities/your_entity.dart)
- Define Model that extends
EntityModel(data/models/your_model.dart) - Configure DI in
injection_container.dart - Create UI using
GenericListBloc
See lib/core/features/USAGE_EXAMPLE.md for detailed examples.
The Secretary feature demonstrates the generic library usage. It was created with only:
- 3 files: Entity, Model, and UI page (~200 lines total)
- DI configuration: ~40 lines in injection_container.dart
- Full functionality: Caching, error handling, empty states, document opening
Compare this to the older Preachers feature which required ~15 files and ~800 lines of code!
The project uses GitHub Actions for continuous deployment:
- Builds are automatically created on workflow dispatch
- Deployment to Google Play Console (alpha, beta, production tracks)
- Credentials are injected securely via GitHub Secrets
GOOGLE_SERVICE_ACCOUNT: Google service account JSON (as a JSON string)ANDROID_KEYSTORE_BASE64: Base64-encoded Android keystoreANDROID_KEYSTORE_PASSWORD: Android keystore passwordGOOGLE_SERVICE_ACCOUNT_KEY: Google Play Console service account key
- Never commit
.env- it's already in.gitignore - Never commit
service_account.json- keep credentials secure - The app uses
enviedto obfuscate credentials at compile time - Credentials are encrypted in the compiled binary (much more secure than
--dart-define) - The hardcoded credentials have been removed from the source code
envied provides compile-time obfuscation and encryption of environment variables:
- Compile-time encryption: When you run
build_runner, envied encrypts your credentials - Obfuscated output: The generated
env.g.dartcontains encrypted values - Runtime decryption: The app decrypts values at runtime (very fast)
- Binary protection: Much harder to extract credentials from the compiled app compared to
--dart-define
This means the app couldn't find the credentials. Make sure:
- You've created the
.envfile - You've run
dart run build_runner build --delete-conflicting-outputs - The
env.g.dartfile was generated successfully
This means you need to run build_runner:
dart run build_runner build --delete-conflicting-outputsEnsure your .env follows this structure:
GOOGLE_SERVICE_ACCOUNT={"type":"service_account","project_id":"..."}
Note: The service account JSON should be on a single line without quotes around it.