The Good Wallet (TGW) is a novel altruistic payment system, where people can transfer money into a fund that is committed to donations. We want to empower individuals and communities to more easily engage in philanthropic activities and create a positive impact.
The following should give a rough outline about the current technical setup of TGW. We use a separate repository for the server-side backend code where we use google cloud functions, GoodWalletCF.
- Transfer money (Money from bank to TGW)
- To other users' Good Wallets or money pools
- via credit card payment (gpay / apple pay for native mobile version, check feasibility for PWA) or Paypal
- Option to browse charities and donate (money from TGW foundation to project)
- Keep up-to-date databank for different effective causes. Use GlobalGivingAPI to fetch data and keep up-to-date in firestore
- Donate from Good Wallet, money pool, or bank account directly (money from user to project, difficulties with tax deductions?) using GlobalGivingAPI
- User favourites option
- Recurring donations
- Money pools
- Create money pool and invite other users to raise together
- Disburse money pool to multiple users
- Statistics and user data
- User donation statistics (history, area, ...)
- User fundraising statistics (history, source, ...)
- Project donation statistics (total donations from TGW users, user top picks, ...)
- Option to make profile page public of user with donations
- API
- API to allow other platforms to integrate TGW
- Authentication via API
- Send money to user Good Wallet via TGW API endpoints
- Manage money pools with API endpoints
- "Team" Accounts
- User's can join "teams" e.g. for corporate money pooling
- Keep track of team donations / fundraising
As app architecture and state management solution we use stacked by FilledStacked which realizes an MVVM architecture in flutter [stacked] (see below for how to get started with stacked). We use Firebase as a serverless backend solution.

- firestore_api.dart:
- everything related to reads, writes, ... to firestore
- global_giving_api.dart:
- handles donations, see API doc
- cloud functions:
- A good chunk of code needs to be implemented as cloud functions, mainly the part where money is transferred.
- user_service.dart:
- authentication
- exposes current user
- handles user settings like favorites
- payment_service.dart:
- connection to actual payment processor (e.g. stripe)
- transfers_history_service.dart:
- exposes list of transfers history (incoming / outgoing money from peer-2-peer transfers, donations, money pool transactions, ...
- projects_service.dart:
- all functionality related to projects
- exposes list of projects
- money_pools_service.dart:
- functionality to create, update, delete, disburse money pools and inviting users to it and everything else related to money pools
- exposes list of money pools
The following describes a few guidelines that we would like to follow when contributing to this project.
- We use the stacked package by FilledStacks which provides a very clean solutions for state management and realizing an MVVM architecture in flutter [stacked]
- This blog post provides a nice introduction to the stacked architecture
- Highly recommended to watch the architecture tutorials summarized [FilledStack's github repo] (especially the tutorials 48, 49, 50). We basically set up the code like explained there.
- From the FilledStacks I can additionally recommend the tutorials starting from number 38 as well as the new [boxt'out series].
- Below the most important design & architecture prinicples
Stacked Architecture Principles (see here)
The architecture functionally consists of only 3 parts. Views, ViewModels and Services. Views on top, the closest to the user, ViewModels are below that taking input from the Views and Services below that which is what the ViewModel's make use of to provide functionality. That's it. It comes with some rules that I highly recommend not to break.
- Views should never MAKE USE of a service directly.
- Views should contain zero to (preferred) no logic. If the logic is from UI only items then we do the least amount of required logic and pass the rest to the ViewModel.
- Views should ONLY render the state in its ViewModel.
- 1 View has 1 ViewModel
- ViewModels for widgets that represent page views are bound to a single View only.
- ViewModels may be re-used if the UI requires the exact same functionality.
- ViewModels should not know about other ViewModels** Along with the 3 pillars above, something to consider is that any service / class that is not a ViewModel, that makes use of multiple services should be called a manager. This is something that would help distinguish better in code which services depend on other services and which are stand alone services. This is not a HARD rule but it's something to consider.
See application setup in stacked README.
- Create files for new view and viewmodel in appropriate directory
- Register view in routes in
lib/app/app.dart - run code generator to generate routes and dependencies
flutter pub run build_runner build --delete-conflicting-outputs, or use script:./runCodeGen.sh - navigate to new screen with navigation service, e.g.:
Future navigateToTransferView() async {
await _navigationService.navigateTo(Routes.transferView);
}
- Create service class and register it as a dependency in
lib/app/app.dartas a lazy singleton - run code generator (see above)
Documentation of classes and functions is very useful and is desired.
Sometimes a well chosen naming is good enough, however, to describe what is happening.
What is very important and useful is to add reasonable log outputs to your code, especially in the service classes. You can instantiate a custom logger with getLogger("<name of file>") where we follow the convention to name the logger after the file it is used in. Please consider these guidelines to write logs and also see logger on pub.dev.
We follow the git feature branch workflow, see for example here.
- Learning UI: Layouts in Flutter
- Flutter Step-by-step guide (blog post)
- Official flutter material
- Use proper Flutter code formatting [flutter formatting]
- Read style guides [official dart page], [random medium article]
- Take your time to think about meaningful names for variables/classes/filename/...everything. Will help sooo much to understand the code later on
I think we should follow the feature branch workflow [explanation]. We're quite a few people and I think it would be nice to always have one version of the app that is state-of-the-art and more importantly works!; this is the version in the master branch (please read about what branches are in git e.g. on the above webpage). Whenever you work on the code, just create a new branch and commit your changes in this branch first. Then we don't interefere with each other in the development and we can also discuss the code in github pull requests!
checkout latest master in your working directory and get updates from remote
git checkout master; git pull
create new branch and directly check it out (with -b)
git checkout -b my-awesome-new-feature
Now, develop your new feature, push it, and create a pull-request on github webpage with potentially assigning the issue. By pushing early we have the option to talk about details in the pull request during ongoing developments and can help each other. To push the branch, do something like this:
git push --set-upstream origin my-awesome-new-feature
I can only recommend to adopt a “commit early, push often” policy during feature developments ([see nice read]). The above command doesn't push anything if you haven't committed a change yet. A commit is (NOT LIKE IN SVN!) not uploaded!!! (that's what git push does) but only creates a local snapshot of the current code.
git commit -m "add awesome function" path/to/awesome/function;
(I usually use git add file1 file2 and then commit everything at once with git commit -m "my changes".)
Once you think you are done, check for potential changes that happened to master in the meantime (from other people), merge them into your branch with
git pull origin master # (one command to fetch updates in remote and merge master)
Here, you might encounter cases where you can’t merge because of local changes that aren’t committed or you will get merge conflicts you need to resolve. If you have done changes to files that you want to disregard you can e.g. stash them with git stash or just checkout the file from the remote master with git checkout origin/master file/with/your/wrong/changes and then git pull origin master again. Afterwards, push your branch once more to the remote with
git push
You can also nicely follow the changes in the code in the github pull request and once you think everything is working and nice, you can merge it to master!
Afterwards, you might want to switch to master again in your local working dir and get the changes from the remote.
git checkout master; git pull
