-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Major codebase cleanup #415
Conversation
…t for wallet services
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general remarks:
-
Good first chunk of refactoring to remove some Cubit in Cubit’s already and start with repository classes.
-
But I think the scope of the current repositories is still too big and should be limited as much as possible to just storing and retrieving data, for example the NetworkRepository now also creates a blockchain object for the wallet. I think a cleaner flow would be: NetworkRepository is only responsible for storing and retrieving the settings concerning the network, then a WalletRepository or WalletService is responsible for creating the blockchain instance it needs. Both the NetworkRepository as the WalletRepository/Service can be injected into the BloC or Service that needs to initialise the wallets. In that BLoC, the NetworkRepository can then be used to retrieve the current network settings from local storage and these can then be passed to the WalletRepository/Service to initialise it with those correct settings. This way they are both decoupled from each other and the BLoCs or Services can use both. Now having the creation of the bdk Blockchain instance in the NetworkRepository is too tightly coupled code. In general I think we should improve by keeping the repositories as limited as possible to just storing and retrieving data. Initializing things or using this data should move to higher layers like Services for global reusable things or to BLoCs for feature specific logic.
-
If the scope of repositories is more bounded, we can organise the architecture a lot better in different layers and make sure there is a clear pattern and consistency in responsibilities of every layer, what is injected into what and how data and calls flow between them. For example, normally repositories get injected to services so that the services can build functionality that needs data from different repositories, but the AppWalletsRepository keeps a list of WalletServices, and the repository creates the services, that’s kind of the other way around. If we keep a more consistent pattern of layers and how injections, data and calls flow through them, the code will become more understandable and easier to extend on.
-
Another practice I think we should apply more in the future is the use of abstract classes/interfaces. This to also create less coupled and more reusable code and try to keep external dependencies like bdk limited to only a secure BitcoinWalletService class, while implementing a general WalletService class for all wallet types. Using abstract classes and then implementing concrete types can also eliminate the need to check for isLiquid, isBitcoin etc, which is repeated a lot in the code.
-
I think we can still improve a lot in how the BLoC pattern is applied:
- The list of WalletBloc's in the AppWalletBlocs cubit state is still a Bloc in another Bloc, which is an anti-pattern and should also be able to be done differently.
- States still do not really have different states in the sense of having a Status enum with the different states this BloC can be in (e.g. inital, loading, success, failure) or having a sealed class for shared properties and subclasses for every distinct state/step a feature or BLoC can be in. Without this, the BLoC pattern can not really be used as effective and more code and if/else’s are needed to figure out what state the BloC is really in now which leads to less maintainable and understandable code.
- The CurrencyCubit still holds state like amounts that logically should be part of other BloCs like the amount to send should be managed by the SendBloc and the amount to receive should be managed by the ReceiveBloc. The CurrencyCubit’s state should be limited to the available and selected currencies and the exchange rate in my opinion, they should not manage state of different features. I think this is a general problem that various Cubits are defined on a global level, while their State and its values are logically more part of other BLoC’s of other features.
I also think that to continue this BLoC refactoring we can do it more gradually bloc per bloc, starting with the easiest maybe like the NetworkBloc, then the CurrencyCubit and everything involved with that (currency repository, Currency cubit state, separating the amount from it and adding it to the send and receive state itself etc.), then the rest of the ReceiveCubit, then the rest of the SendCubit etc…
This PR was pretty big to review all at once and pretty difficult to really understand all implications and changes.
@@ -515,6 +516,10 @@ class Wallet with _$Wallet { | |||
List<SwapTx> swapsToProcess() { | |||
return swaps.where((swap) => swap.proceesTx() && !swap.failed()).toList(); | |||
} | |||
|
|||
int balanceSats() => balance ?? 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not change the balance field/parameter to balanceSats directly with a default value of 0 instead of having balance which is unclear if it is in sats or btc or fiat and then having a function for balanceSats which just returns balance or 0… I think this can be clearer by just replacing the balance field for balanceSats directly with a default 0 value.
|
||
int balanceSats() => balance ?? 0; | ||
|
||
String balanceStr() => ((balance ?? 0) / 100000000).toStringAsFixed(8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The balanceStr
seems to be the balance in btc and as a String then, but the fact that it is in btc is not clear from the name. balanceBtc
would be a better name I think, and I would leave it as a numeric value (preferably Decimal
type) and leave String formatting up to the presentation/UI layer and not the model. The intl
package number formatting capabilities (NumberFormat.currency) can be used in the Widgets for a formatted String of a currency balance like this.
@@ -39,8 +40,8 @@ class DeepLink { | |||
Future<Err?> handleUri({ | |||
required String link, | |||
// required SettingsCubit settingsCubit, | |||
required HomeCubit homeCubit, | |||
required NetworkCubit networkCubit, | |||
required HomeBloc homeCubit, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HomeBloc
is renamed, but the variable is still named homeCubit
.
Same for networkCubit
.
@@ -13,6 +13,7 @@ class StorageKeys { | |||
static const wallets = 'wallets'; | |||
static const settings = 'settings'; | |||
static const network = 'network'; | |||
static const networkReposity = 'networkReposity'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear why a repository itself should have a key to be stored itself...?
|
||
final List<Wallet> walletsWithEnoughBalance = []; | ||
|
||
for (final walletBloc in wallets) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename walletBloc to wallet since it is not a Bloc anymore.
|
||
class WalletService { | ||
WalletService({ | ||
required Wallet wallet, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be clearer if a WalletService creates a Wallet instance itself in its constructor or some create
or init
function, instead of already getting one injected. Definitely if it also gets the WalletStorageRepository passed, which is good. Having that repository should be what the service needs to retrieve the Wallet instead of getting injected both.
required WalletSync walletSync, | ||
required WalletBalance walletBalance, | ||
required WalletAddress walletAddress, | ||
required WalletCreate walletCreate, | ||
required WalletTx walletTransaction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having the WalletCreate, WalletSync and WalletAddress as “global” instances through locator being passed into the service, wouldn't it be clearer if the WalletService itself is a Global instance and it just has the functions from WalletSync etc in it instead of getting them injected in? It could create and keep a Wallet
instance in memory instead of having to pass it to everyone of these classes.
import 'package:bb_mobile/address/bloc/address_state.dart'; | ||
import 'package:bb_mobile/wallet/bloc/event.dart'; | ||
import 'package:bb_mobile/wallet/bloc/wallet_bloc.dart'; | ||
import 'package:flutter_bloc/flutter_bloc.dart'; | ||
|
||
class AddressCubit extends Cubit<AddressState> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this Cubit can be separated and replaced with repositories and wallet services to make things more straight forward.
@@ -35,6 +37,12 @@ import 'package:gap/gap.dart'; | |||
import 'package:go_router/go_router.dart'; | |||
import 'package:google_fonts/google_fonts.dart'; | |||
|
|||
class AppWalletBlocs extends Cubit<List<WalletBloc>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still a WalletBloc inside another Cubit, this is really an anti-pattern.
Why not have methods in the HomeBloc itself to clearWallets or update the Wallet’s that are available? If this is possible to do from the home page by the user, this should just be part of the HomeBloc I think.
|
||
for (final bloc in blocs) { | ||
context | ||
.read<HomeLoadingCubit>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having a HomeLoadingCubit , shouldn’t we just have a loading state in the HomeBloc state itself?
If we pass the wallet repositories to the HomeBloc, the Bloc itself can listen to events of it and change its state from and to Loading.
INFO: further clean up, rearranging and renaming will be done after this PR is merged
Removed all/most bloc-to-bloc communications
WALLET SERVICE
APP WALLET REPOSITORY
NETWORK REPOSITORY
APP WALLETS BLOC