jamiewest / signalr_core

ASP.NET Core SignalR Dart Client
https://pub.dev/packages/signalr_core
MIT License
90 stars 63 forks source link

The underlying connection was closed before the hub handshake could complete. #114

Open MrAzimzadeh opened 1 month ago

MrAzimzadeh commented 1 month ago

Full Code

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:legalis/api/api_service.dart';
import 'package:legalis/provider/chat/chat_detail_provider.dart';
import 'package:legalis/provider/communication/communication_provider.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:signalr_netcore/itransport.dart';
import 'package:signalr_netcore/json_hub_protocol.dart';
import 'package:signalr_netcore/signalr_client.dart';

class ChatPage extends StatefulWidget {
  ChatPage({super.key, required this.id});

  final String? id;

  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  late TextEditingController _controller;
  late HubConnection _hubConnection;
  bool _isConnected = false;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController();
    _initSignalR();
  }

  Future<void> _initSignalR() async {
    ApiService apiService = ApiService();
    JsonHubProtocol protocol = JsonHubProtocol();
    _hubConnection = HubConnectionBuilder()
        .withUrl(
          "https://legalis-api.webconsole.az/hubaction",
          options: HttpConnectionOptions(
            transport: HttpTransportType.WebSockets,
            accessTokenFactory: () async {
              return await _getAccessToken(apiService) ?? '';
            },
            skipNegotiation: true,
            logger: null,
          ),
        )
        .withAutomaticReconnect()
        .build();

    // SignalR bağlantısını başlatmayı deneyin
    await _startConnection();
  }

  Future<String?> _getAccessToken(ApiService apiService) async {
    try {
      String? accessToken = await apiService.getAccessToken();
      Logger().i("Access Token: $accessToken");
      return accessToken;
    } catch (e) {
      Logger().d("Error fetching access token: $e");
      return null;
    }
  }

  Future<void> _startConnection() async {
    try {
      _hubConnection.onclose(({error}) => _onConnectionClose(error));
      _hubConnection.on("ReceiveMessage", (message) {
        Logger().i("Received: $message");
      });

      await _hubConnection.start();
      _isConnected = true;
      Logger().i("SignalR connected.");
      setState(() {});
    } catch (e) {
      Logger().e("Error starting SignalR connection: $e");
      await _retryConnection();
    }
  }

  void _onConnectionClose(Exception? error) {
    _isConnected = false;
    if (error != null) {
      Logger().e("Connection closed with error: $error");
      _retryConnection();
    } else {
      Logger().i("Connection closed gracefully.");
    }
    setState(() {});
  }

  Future<void> _retryConnection() async {
    const int maxRetries = 5;
    int retryCount = 0;

    while (retryCount < maxRetries && !_isConnected) {
      try {
        Logger().i("Retrying SignalR connection: Attempt ${retryCount + 1}");
        await _hubConnection.start();
        _isConnected = true;
        Logger().i("SignalR reconnected successfully.");
        break;
      } catch (e) {
        Logger().e("Retry $retryCount failed: $e");
        retryCount++;
        await Future.delayed(Duration(seconds: 5));
      }
    }

    if (!_isConnected) {
      Logger().e("Failed to reconnect after $maxRetries attempts.");
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    _hubConnection.stop(); // Bağlantıyı kapatmayı unutmayın
    super.dispose();
  }

  Future<String> refreshToken() async {
    final _prefs = await SharedPreferences.getInstance();
    ApiService apiService = ApiService();
    await apiService.init();
    var refreshToken = _prefs.getString('refresh_token');
    if (refreshToken != null) {
      try {
        final response = await apiService.dio.post(
          'Auth/refresh-token-login',
          data: {'refreshToken': refreshToken},
        );

        if (response.statusCode == 200) {
          final newAccessToken = response.data['access_token'];
          _prefs.setString('access_token', newAccessToken);
          _prefs.setString('refresh_token', response.data['refresh_token']);
          return newAccessToken;
        } else {
          throw Exception('Failed to refresh token: ${response.statusCode}');
        }
      } catch (e) {
        Logger().e("Error refreshing token: $e");
        throw e;
      }
    }
    throw Exception('Refresh token is null');
  }

  Future<void> sendMessage(String message, {required String receiverId}) async {
    if (_isConnected) {
      await _hubConnection.invoke("SendMessage",
          args: [message, receiverId]).onError((error, stackTrace) {
        Logger().e("Error sending message: $error");
      });
    } else {
      Logger().e("Cannot send message: Not connected to the server.");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF000E2B),
      appBar: AppBar(
        elevation: 0,
        automaticallyImplyLeading: false,
        leadingWidth: 0,
        shadowColor: Colors.transparent,
        foregroundColor: Colors.transparent,
        surfaceTintColor: Colors.transparent,
        backgroundColor: const Color(0xFF000E2B),
        title: Row(
          children: [
            Container(
              width: 14,
              height: 14,
              decoration: const ShapeDecoration(
                color: Color(0xFF34A853),
                shape: OvalBorder(),
              ),
            ),
            const SizedBox(width: 10),
            const Expanded(
              child: SizedBox(
                child: Text(
                  'Məhəmməd Məhəmməd Məhəmməd Məhəmməd Məhəmməd Məhəmməd Vekil',
                  style: TextStyle(
                    color: Colors.white,
                    overflow: TextOverflow.ellipsis,
                    fontSize: 20,
                    fontWeight: FontWeight.w600,
                    letterSpacing: 0.38,
                  ),
                ),
              ),
            ),
          ],
        ),
        actions: [
          GestureDetector(
            onTap: () {
              context.pushNamed('callpage', queryParameters: {'id': '1'});
            },
            child: Container(
              width: 46,
              height: 46,
              margin: const EdgeInsets.only(right: 20),
              child: SvgPicture.asset('assets/icons/call_user.svg'),
            ),
          )
        ],
      ),
      body: SafeArea(
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 20),
          child: Column(
            children: [
              Expanded(
                child: Column(
                  children: [
                    Expanded(
                      child: Consumer<ChatDetailProvider>(
                        builder: (context, provider, child) {
                          return ListView.builder(
                            padding: const EdgeInsets.only(
                              top: 20,
                            ),
                            reverse: true,
                            itemCount: provider.chatList.length,
                            itemBuilder: (context, index) {
                              bool isSameUser = index > 0 &&
                                  provider.chatList[index].isMe ==
                                      provider.chatList[index - 1].isMe;
                              return Align(
                                alignment: provider.chatList[index].isMe
                                    ? Alignment.centerRight
                                    : Alignment.centerLeft,
                                child: Stack(
                                  children: [
                                    Container(
                                      padding: EdgeInsets.only(
                                        top: 12,
                                        left: 10,
                                        bottom: provider.chatList[index].isMe
                                            ? 13
                                            : 13,
                                        right: 7,
                                      ),
                                      margin: EdgeInsets.only(
                                        bottom: isSameUser ? 5 : 10,
                                        top: isSameUser ? 5 : 10,
                                      ),
                                      width: MediaQuery.of(context).size.width *
                                          0.6,
                                      decoration: BoxDecoration(
                                        color: provider.chatList[index].isMe
                                            ? const Color.fromRGBO(3, 44, 96, 1)
                                            : const Color(0xFF3467E4),
                                        borderRadius: BorderRadius.only(
                                          topLeft: const Radius.circular(20),
                                          topRight: const Radius.circular(20),
                                          bottomRight:
                                              provider.chatList[index].isMe
                                                  ? Radius.zero
                                                  : const Radius.circular(20),
                                          bottomLeft:
                                              provider.chatList[index].isMe
                                                  ? const Radius.circular(20)
                                                  : Radius.zero,
                                        ),
                                      ),
                                      child: Text(
                                        provider.chatList[index].txt,
                                        style: const TextStyle(
                                          color: Colors.white,
                                          fontSize: 16,
                                          fontWeight: FontWeight.w500,
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                              );
                            },
                          );
                        },
                      ),
                    ),
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: Consumer<CommunicationProvider>(
                        builder: (context, provider, child) {
                          return provider.isConnected
                              ? Container(
                                  margin: const EdgeInsets.only(
                                      left: 0, right: 0, bottom: 16),
                                  child: Row(
                                    mainAxisAlignment:
                                        MainAxisAlignment.spaceBetween,
                                    children: [
                                      Expanded(
                                        child: Container(
                                          margin:
                                              const EdgeInsets.only(right: 11),
                                          constraints: const BoxConstraints(
                                            minHeight: 52,
                                            maxHeight: 100,
                                          ),
                                          child: TextField(
                                            controller: _controller,
                                            cursorColor: Colors.white,
                                            maxLines: 3,
                                            minLines: 1,
                                            keyboardType:
                                                TextInputType.multiline,
                                            style: const TextStyle(
                                              color: Colors.white,
                                              fontSize: 18,
                                              fontWeight: FontWeight.w400,
                                            ),
                                            decoration: InputDecoration(
                                              hintText:
                                                  '${AppLocalizations.of(context)?.enterMessage}...',
                                              hintStyle: const TextStyle(
                                                color: Color(0xFFABB0BC),
                                                fontSize: 18,
                                                fontWeight: FontWeight.w400,
                                              ),
                                              border: OutlineInputBorder(
                                                borderRadius:
                                                    BorderRadius.circular(20),
                                                borderSide: BorderSide.none,
                                              ),
                                              filled: true,
                                              fillColor: const Color.fromRGBO(
                                                  51, 62, 85, 1),
                                            ),
                                          ),
                                        ),
                                      ),
                                      GestureDetector(
                                        onTap: () {
                                          if (_controller.text.isNotEmpty) {
                                            Provider.of<ChatDetailProvider>(
                                                    context,
                                                    listen: false)
                                                .sendMessage(_controller.text,
                                                    receiverId:
                                                        widget.id ?? '');
                                            _controller.clear();
                                          }
                                        },
                                        child: Container(
                                          width: 52,
                                          height: 52,
                                          child: SvgPicture.asset(
                                            'assets/icons/send_icon.svg',
                                            width: 52,
                                            height: 52,
                                          ),
                                        ),
                                      ),
                                    ],
                                  ),
                                )
                              : Container(
                                  width: MediaQuery.of(context).size.width,
                                  height: 44,
                                  alignment: Alignment.center,
                                  margin: const EdgeInsets.only(
                                      left: 0, right: 0, bottom: 16),
                                  padding: const EdgeInsets.all(10),
                                  decoration: ShapeDecoration(
                                    color: const Color(0xFF6A6A6A),
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(10),
                                    ),
                                  ),
                                  child: const Text(
                                    'Bu söhbət artıq yekunlaşıb.',
                                    style: TextStyle(
                                      color: Colors.white,
                                      fontSize: 16,
                                      fontWeight: FontWeight.w500,
                                    ),
                                  ),
                                );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
MrAzimzadeh commented 1 month ago

My Logs ──────────────────────────────────────────────────────────── E/flutter (21550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: WebSocketChannelException: Instance of 'WebSocketException' E/flutter (21550): I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:110:18) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ ⛔ Retry 2 failed: The underlying connection was closed before the hub handshake could complete. I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:104:18) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ 💡 Retrying SignalR connection: Attempt 4 I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._getAccessToken (package:legalis/screens/pages/message/chat_page.dart:62:16) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ 💡 Access Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZWdoZ2hTYWxhbV84ODA3YmVkNC1jYjIzLTRiYmYtYWEwMi01YzdlNjI3NDdiNGIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImRiMTk1NjRiLWViODctNDUxYS1hODA5LWUxZjlmNmU3MjI1ZSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI6InNhbGFtQGdtYWlsLmNvbSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluIiwiZXhwIjoxNzI2NzU0NjExLCJpc3MiOiJodHRwczovL2xhd3llci5heiIsImF1ZCI6Imh0dHBzOi8vbGF3eWVyLmF6In0.Bi8hc1Lm-0VEgc7Dy-mbzUPI2mFY-zsLyrvUezGdNXI I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── E/flutter (21550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: WebSocketChannelException: Instance of 'WebSocketException' E/flutter (21550): I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:110:18) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ ⛔ Retry 3 failed: The underlying connection was closed before the hub handshake could complete. I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:104:18) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ 💡 Retrying SignalR connection: Attempt 5 I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._getAccessToken (package:legalis/screens/pages/message/chat_page.dart:62:16) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ 💡 Access Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZWdoZ2hTYWxhbV84ODA3YmVkNC1jYjIzLTRiYmYtYWEwMi01YzdlNjI3NDdiNGIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImRiMTk1NjRiLWViODctNDUxYS1hODA5LWUxZjlmNmU3MjI1ZSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI6InNhbGFtQGdtYWlsLmNvbSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluIiwiZXhwIjoxNzI2NzU0NjExLCJpc3MiOiJodHRwczovL2xhd3llci5heiIsImF1ZCI6Imh0dHBzOi8vbGF3eWVyLmF6In0.Bi8hc1Lm-0VEgc7Dy-mbzUPI2mFY-zsLyrvUezGdNXI I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── E/flutter (21550): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: WebSocketChannelException: Instance of 'WebSocketException' E/flutter (21550): I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:110:18) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ ⛔ Retry 4 failed: The underlying connection was closed before the hub handshake could complete. I/flutter (21550): └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── I/flutter (21550): │ #0 _ChatPageState._retryConnection (package:legalis/screens/pages/message/chat_page.dart:117:16) I/flutter (21550): │ #1 I/flutter (21550): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ I/flutter (21550): │ ⛔ Failed to reconnect after 5 attempts. I/flutter (21550): └─────────────────────────────────────────