WalletConnect / Web3ModalFlutter

The Web3Modal for WalletConnect built using Flutter.
https://pub.dev/packages/web3modal_flutter
Apache License 2.0
35 stars 41 forks source link

the disconnect method doesn't seem to wait for asynchronous calls to complete. #131

Closed AndriiVivcharenko closed 3 months ago

AndriiVivcharenko commented 3 months ago

Describe the bug if I call the disconnect method before the openModal method, it doesn't disconnect the session properly because I get a blank screen in the modal window instead of a list of wallets to connect. Adding a 1-second delay after disconnect seems like a quick workaround to solve this problem. Otherwise, I'll have to close the modal window and reopen it every time I get a blank screen.

To Reproduce Package version: 3.2.0

  1. Connect your wallet
  2. Disconnect and then reconnect as below:

Doesn't Work

    await _w3mService!.disconnect();
    await _w3mService!.openModal(context);

Works

await _w3mService!.disconnect();
await Future.delayed(const Duration(seconds: 1));
await _w3mService!.openModal(context);

Code example


class WalletConnectPage2 extends StatefulWidget {
  const WalletConnectPage2({super.key});

  @override
  State<WalletConnectPage2> createState() => _WalletConnectPage2State();
}

class _WalletConnectPage2State extends State<WalletConnectPage2> {
  bool _initialized = false;
  W3MService? _w3mService;

  @override
  void initState() {
    super.initState();
    _initWcClient();
  }

  Future<void> _initWcClient() async {
    _w3mService = W3MService(
      projectId: projectId,
      metadata: walletConnectMetadata,
    );
    await _w3mService!.init();

    _initialized = true;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text(
            'Wallet Connect Page',
          ),
          const SizedBox(
            height: 12,
          ),
          if (_initialized)
            ElevatedButton(
              onPressed: () async {
                await _w3mService!.disconnect();
                await _w3mService!.openModal(context);
              },
              child: const Text(
                "Connect Wallet",
              ),
            )
          else
            const CircularProgressIndicator(),
        ],
      ),
    ));
  }
}

Expected behavior the disconnect method should completely clear the session. The blank modal screen should not occur.

Screenshots image

Smartphones:

quetool commented 3 months ago

Hello @AndriiVivcharenko! Thanks for the report. Honestly there's nothing I can do, disconnect is not supposed to be used that way, when you hit disconnect button a request to the relay service is being done so even if the disconnect() method finishes execution it could happen (and will happen in 99.99% of the time) that the relay didn't disconnect yet.

I would recommend instead to subscribe to onModalConnect and onModalDisconnect events to enable disable actions/buttons. https://docs.walletconnect.com/web3modal/flutter/events

Also, if you disconnect every time you want to open the modal then the user won't be able to enter the "Account Screen", could I ask why you want to disconnect every time?

I would do something like this:

import 'package:flutter/material.dart';
import 'package:web3modal_flutter/utils/util.dart';
import 'package:web3modal_flutter/web3modal_flutter.dart';

class WalletConnectPage2 extends StatefulWidget {
  const WalletConnectPage2({super.key});

  @override
  State<WalletConnectPage2> createState() => _WalletConnectPage2State();
}

class _WalletConnectPage2State extends State<WalletConnectPage2> {
  bool _initialized = false;
  W3MService? _w3mService;

  @override
  void initState() {
    super.initState();
    _initWcClient();
  }

  Future<void> _initWcClient() async {
    _w3mService = W3MService(
      projectId: projectId,
      metadata: walletConnectMetadata,
      ),
    );

    _w3mService!.onModalConnect.subscribe(_update);
    _w3mService!.onModalDisconnect.subscribe(_update);

    await _w3mService!.init();

    _initialized = true;
    setState(() {});
  }

  void _update(dynamic event) {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Wallet Connect Page',
            ),
            const SizedBox(
              height: 12,
            ),
            if (_initialized)
              ElevatedButton(
                onPressed: () async {
                  // await _w3mService!.disconnect();
                  await _w3mService!.openModal(context);
                },
                child: _w3mService!.isConnected
                    ? Text(Util.truncate(_w3mService!.session!.address!))
                    : const Text("Connect Wallet"),
              )
            else
              const CircularProgressIndicator(),
          ],
        ),
      ),
    );
  }
}
AndriiVivcharenko commented 3 months ago

Also, if you disconnect every time you want to open the modal then the user won't be able to enter the "Account Screen", could I ask why you want to disconnect every time?

I want to disconnect every time because we have a two-step connection process now, so the second step happens outside of web3modal. In some cases, users may get a state where web3modal is connected but the second step hasn't been completed or hasn't gone through and we can't always verify that. And we don't really need to use the "Account Screen", That's why we wanted to make sure that every time the user starts the connection process again, they always have the option to select the wallet again.

Using onModalDisconnect makes more sense now. I just tried it and it looks like the best way to go. It just didn't seem obvious that using disconnect method in this way would not work. Thanks for the quick reply!

quetool commented 3 months ago

I get the feedback and I appreciate it! Still your's is kind of an edge case scenario. Maybe it's better if you call disconnect() when receiving onModalConnect event? So something like this:

  1. Connect with Web3Modal
  2. When onModalConnect is triggered you grab whatever data you need and then call service.disconnect()
  3. You now launch the second-step connection.

This way every time the user taps on service.openModal() the service will be disconnected? Just an idea...

Anyway I'll close this for now as it doesn't seems an issue "per se" but feel free to ask for advices or help!