Bad state: No element
Closed this issue · 10 comments
Describe the bug
Method getNetworkById
thrown an error when app running in flutterMode: release
In debug
mode, all works fine!
Error:
flutter: Bad state: No element
Method:
static ReownAppKitModalNetworkInfo? getNetworkById(
String namespace,
String chainId,
) {
return getAllSupportedNetworks(namespace: namespace).firstWhere(
(e) => e.chainId == chainId,
);
}
SDK version:
reown_appkit: ^1.2.0-alpha01
@quetool Can you please check and advise?
It's tough to find a source of the issue because of the release
mode. I hope you will find some time to check it.
Also, under from init
method I received an error too
LateInitializationError: Field '_bundleId@3327041263' has already been initialized.
Hello @damienissa could you provide a minimum reproducible code of both issues?
It's tough to find a source of the issue because of the release mode. I hope you will find some time to check it.
This is why profile mode exists. You should be able to run your code in profile mode, which is kind of release but with logging.
@quetool
This is my service for working with the appKit.
Also, I can create a simple project with this service and place it on github repo.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:injectable/injectable.dart';
import 'package:reown_appkit/reown_appkit.dart' hide ReownConstants;
import 'networks.dart';
import 'reown_constants.dart';
abstract interface class IReownService {
bool get isConnected;
void init({required BuildContext context});
Stream<DateTime> get onChanged;
void connect({required VoidCallback onConnect});
Future<dynamic> sendToken({
required String receiverAddress,
required double amount,
required String memo,
required String networkCode,
required String assetCode,
});
Future<void> changeNetwork(SupportedNetwork network);
void disconnect();
ReownAppKitModalWalletInfo? get selectedWallet;
}
@Singleton(as: IReownService)
final class ReownService implements IReownService {
late ReownAppKitModal _appKitModal;
bool isInitialized = false;
VoidCallback? _onConnect;
final _streamController = StreamController<DateTime>.broadcast();
@override
Stream<DateTime> get onChanged => _streamController.stream;
@override
bool get isConnected => _appKitModal.isConnected;
@override
ReownAppKitModalWalletInfo? get selectedWallet => _appKitModal.selectedWallet;
@override
void connect({required VoidCallback onConnect}) {
_onConnect = onConnect;
_appKitModal.openModalView();
}
@override
void disconnect() async {
await _appKitModal.disconnect();
}
void notifyListeners(String reason) {
print('IReownService:notifyListeners: $reason');
_streamController.add(DateTime.now());
}
@override
void init({required BuildContext context}) async {
if (isInitialized) return;
if (kDebugMode) {
ReownAppKitModalNetworks.addSupportedNetworks(
NetworkUtils.eip155,
ReownConstants.supportedNetworks,
);
}
_appKitModal = ReownAppKitModal(
context: context,
projectId: ReownConstants.projectId,
enableAnalytics: true,
logLevel: LogLevel.debug,
includedWalletIds: {
ReownConstants.trustWalletId,
ReownConstants.metaMaskWalletId,
},
metadata: const PairingMetadata(
name: '{AppName}',
description: '{Description}',
url: ReownConstants.xboUrl,
icons: [ReownConstants.xboIconUrl],
redirect: Redirect(native: ReownConstants.xboRedirectUrl),
),
);
// Set up listeners for modal events
_appKitModal.onModalConnect.subscribe((ModalConnect? event) {
notifyListeners('onModalConnect');
_onConnect?.call();
});
_appKitModal.onModalUpdate
.subscribe((ModalConnect? event) => notifyListeners('onModalUpdate'));
_appKitModal.onModalNetworkChange.subscribe(
(ModalNetworkChange? event) => notifyListeners('onModalNetworkChange'));
_appKitModal.onModalDisconnect.subscribe(
(ModalDisconnect? event) => notifyListeners('onModalDisconnect'));
_appKitModal.onModalError
.subscribe((ModalError? event) => notifyListeners('onModalError'));
_appKitModal.onSessionExpireEvent.subscribe(
(SessionExpire? event) => notifyListeners('onSessionExpireEvent'));
_appKitModal.onSessionUpdateEvent.subscribe(
(SessionUpdate? event) => notifyListeners('onSessionUpdateEvent'));
_appKitModal.onSessionEventEvent.subscribe(
(SessionEvent? event) => notifyListeners('onSessionEventEvent'));
_appKitModal.appKit!.core.relayClient.onRelayClientConnect.subscribe(
(EventArgs? event) => notifyListeners('onRelayClientConnect'));
_appKitModal.appKit!.core.relayClient.onRelayClientError
.subscribe((EventArgs? event) => notifyListeners('onRelayClientError'));
_appKitModal.appKit!.core.relayClient.onRelayClientDisconnect.subscribe(
(EventArgs? event) => notifyListeners('onRelayClientDisconnect'));
try {
await _appKitModal.init();
isInitialized = true;
notifyListeners('isInitialized');
} catch (e) {
print('IReownService:error: $e');
notifyListeners('isInitialized');
}
}
@override
Future<void> changeNetwork(SupportedNetwork network) async {
if (!isConnected) return;
try {
if (_appKitModal.selectedChain?.chainId == network.chainID) return;
final chain = ReownAppKitModalNetworks.getNetworkById(
NetworkUtils.eip155, network.chainID ?? '');
await _appKitModal.selectChain(chain, switchChain: true);
_appKitModal.launchConnectedWallet();
final chainId = int.tryParse(network.chainID ?? '0');
return await _appKitModal.request(
topic: _appKitModal.session!.topic,
chainId: _appKitModal.selectedChain!.chainId,
request: SessionRequestParams(
method: MethodsConstants.walletSwitchEthChain,
params: [
{'chainId': '0x${chainId?.toRadixString(16)}'}
],
),
);
} catch (e) {
_streamController.addError(e);
print('IReownService:error: $e');
}
}
@override
Future sendToken({
required String receiverAddress,
required double amount,
required String memo,
required String networkCode,
required String assetCode,
}) async {
final network = SupportedNetwork.getNetworkFor(
assetCode: assetCode, networkCode: networkCode);
if (network == null) return;
try {
if (network.contractAddress.isNotEmpty) {
return _sendOtherTokens(
receiverAddress: receiverAddress, amount: amount, network: network);
}
final EthereumAddress receiver = EthereumAddress.fromHex(receiverAddress);
final BigInt weiValue = BigInt.from(amount * 1e18);
final address = _appKitModal.session!.getAddress(NetworkUtils.eip155);
if (address == null) return;
final sender = EthereumAddress.fromHex(address);
final transaction = Transaction(
from: sender, to: receiver, value: EtherAmount.inWei(weiValue));
_appKitModal.launchConnectedWallet();
return await _appKitModal
.request(
topic: _appKitModal.session!.topic,
chainId: _appKitModal.selectedChain!.chainId,
request: SessionRequestParams(
method: MethodsConstants.ethSendTransaction,
params: [transaction.toJson()],
),
)
.catchError((error) {
print('IReownService:error: $error');
_streamController.addError(error);
});
} catch (e) {
_streamController.addError(e);
print('IReownService:error: $e');
}
}
Future<dynamic> _sendOtherTokens({
required String receiverAddress,
required double amount,
required SupportedNetwork network,
}) async {
try {
final EthereumAddress receiver = EthereumAddress.fromHex(receiverAddress);
final int decimals = network.decimals;
final BigInt weiValue =
BigInt.from(amount * BigInt.from(10).pow(decimals).toInt());
final tokenContractAddress =
EthereumAddress.fromHex(network.contractAddress);
final address = _appKitModal.session!.getAddress(NetworkUtils.eip155);
if (address == null) {
print('Error: No sender address found.');
return;
}
final sender = EthereumAddress.fromHex(address);
const bep20Abi = '''
[
{
"constant": false,
"inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "transfer",
"outputs": [{ "name": "", "type": "bool" }],
"type": "function"
}
]
''';
final contract = DeployedContract(
ContractAbi.fromJson(bep20Abi, network.id), tokenContractAddress);
final transaction = Transaction.callContract(
from: sender,
contract: contract,
function: contract.function('transfer'),
parameters: [receiver, weiValue],
);
_appKitModal.launchConnectedWallet();
return await _appKitModal
.request(
topic: _appKitModal.session!.topic,
chainId: network.chainID?.toString() ?? '',
request: SessionRequestParams(
method: MethodsConstants.ethSendTransaction,
params: [transaction.toJson()],
),
)
.catchError((error) {
print('IReownService:error: $error');
_streamController.addError(error);
});
} catch (e) {
print('IReownService:error: $e');
_streamController.addError(e);
}
}
}
Please if you could create a simple project that would help me a lot indeed.
Was it fixed @damienissa? Would be nice to have a description of the problem so it could help others.
Yeah, the problem was with this part of code:
if (kDebugMode) {
ReownAppKitModalNetworks.addSupportedNetworks(
NetworkUtils.eip155,
ReownConstants.supportedNetworks,
);
}
When I run it with release mode, Testnet networks is no longer available and an exception is received.
@quetool This is my service for working with the appKit. Also, I can create a simple project with this service and place it on github repo.
import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:injectable/injectable.dart'; import 'package:reown_appkit/reown_appkit.dart' hide ReownConstants; import 'networks.dart'; import 'reown_constants.dart'; abstract interface class IReownService { bool get isConnected; void init({required BuildContext context}); Stream<DateTime> get onChanged; void connect({required VoidCallback onConnect}); Future<dynamic> sendToken({ required String receiverAddress, required double amount, required String memo, required String networkCode, required String assetCode, }); Future<void> changeNetwork(SupportedNetwork network); void disconnect(); ReownAppKitModalWalletInfo? get selectedWallet; }
Thanks for sharing the code!
Can I ask one question? Is it OK to add such getter with hardcoded namespace string?
String? get currentAddress => _appKitModal.session?.getAddress('eip155');
It stands for Ethereum, but anyway, can that value change over time..? And how to handle user selection of something non-EVM? 🤔
@vitalii-petrun You can get the current namespace based on chainId
and then use this value in getAddress()
final chainId = '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'; // Solana mainnet
final namespace = ReownAppKitModalNetworks.getNamespaceForChainId(chainId); // `solana` namespace
final address = _appKitModal.session?.getAddress(namespace); // Solana address