Проблема с провайдерами и потоками: получатель «subscriptionManager» был вызван на нуль - PullRequest
0 голосов
/ 29 апреля 2020

Первая публикация здесь, и вы моя последняя надежда :(. Я пытаюсь создать очень простое приложение Mqtt, которое использует брокер Ubidots для управления сообщениями. Прежде всего, у меня есть класс MQTTManager:


import 'package:mqttapp/Services/Mqtt_messages_notifier.dart';
import 'package:flutter/cupertino.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqttapp/MQTTStates.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';


class MQTTManager{
  MqttServerClient cliente;
  MQTTMessagesNotifier _messagesNotifier = MQTTMessagesNotifier();
  String identifier;
  String topic;
  String host;


  void initialize({String host, String identifier}){
    MqttServerClient _client = MqttServerClient(host, identifier);
    this.identifier = _client.clientIdentifier;
    this.host = host;
    _client.port = 1883;
    _client.keepAlivePeriod = 20;
    _client.onDisconnected = onDisconnected;
    _client.onConnected = onConnected;
    _client.onSubscribed = onSubscribed;
    _client.logging(on: false);

    final conMess = MqttConnectMessage()
        .withClientIdentifier(identifier)
        .keepAliveFor(20)
        .withWillTopic('willtopic')
        .withWillMessage('willmessage')
        .startClean()
        .withWillQos(MqttQos.atLeastOnce);

    _client.connectionMessage = conMess;
    this.cliente = _client;
  }

  Future connect()async{
    print(cliente.clientIdentifier);
    try {
      MqttClientConnectionStatus result = await cliente.connect('HERE GOES THE UBIDOTS TOKEN, REMOVED IT FOR SECURITY REASONS','');
      return result.state;
    } on Exception catch (e) {
      print('Something went wrong $e');
      disconnect();
      return null;
    }
  }

  void subscription({String topic}) {
    this.topic = topic;
    print('EXAMPLE::Subscribing to the $topic topic');
    cliente.subscribe(topic, MqttQos.atMostOnce);
  }

  void unsubscribe({String topic}) {
    print('unsubscribing from $topic');
    cliente.unsubscribe(topic);
    print('Unsubscribbed!');
  }

  void publish({String topic, String message}) async {
    final builder = MqttClientPayloadBuilder();
    builder.addString(message);
    cliente.publishMessage(topic, MqttQos.atMostOnce, builder.payload);
  }

  void disconnect() async {
    await MqttUtilities.asyncSleep(2);
    print('EXAMPLE::Disconnecting');
    cliente.disconnect();
  }

  /// The subscribed callback
  void onSubscribed(String topic) {
    print('EXAMPLE::Subscription confirmed for topic $topic');
    cliente.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
      final MqttPublishMessage recMess = c[0].payload;
      final String message =
      MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
      _messagesNotifier.messageAlert(message);
    });
  }

  /// The unsolicited disconnect callback
  void onDisconnected() {
    print('EXAMPLE::OnDisconnected client callback - Client disconnection');
    if (cliente.connectionStatus.returnCode == MqttConnectReturnCode.solicited) {
      print('EXAMPLE::OnDisconnected callback is solicited, this is correct');
    }
  }

  /// The successful connect callback
  void onConnected() {
    _messagesNotifier.managerAlert(this.cliente);
    print(
        'EXAMPLE::OnConnected client callback - Client connection was sucessful');
  }

  /// Pong callback
  void pong() {
    print('EXAMPLE::Ping response client callback invoked');
  }
}

, который управляет состоянием соединений. Для функции обратного вызова onSubscribeed и для функции обратного вызова onConnected я использую две функции, которые я объявил в другом классе, который управляет поставщиками:

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_server_client.dart';

class MQTTMessagesNotifier with ChangeNotifier {
  String _messageOfNotifier='';
  MqttServerClient _client;

  void messageAlert(String text){
    this._messageOfNotifier = text;
    notifyListeners();
  }
  void managerAlert(MqttServerClient client){
    this._client = client;
    notifyListeners();
  }

  String get message => _messageOfNotifier;
  MqttServerClient get client => _client;

}

Целью функции messageAlert является получение сообщения и его сохранение в свойстве класса _messageOfNotifier. То же самое с managerAlert, но извлекает MqttServerClient cliente из класса и сохраняет его в свойстве с именем _cliente. У меня также есть класс, который отображает graphi c stuff:

import 'package:flutter/material.dart';
import 'package:mqttapp/Services/MQTT.dart';
import 'package:mqttapp/screens/Mqtt_screens/Mqtt_Publish.dart';
import 'package:mqttapp/screens/Mqtt_screens/Mqtt_Subscribe.dart';
import 'package:provider/provider.dart';
import 'package:mqttapp/Services/Mqtt_messages_notifier.dart';

class ScreensWrapper extends StatefulWidget {
  @override
  _ScreensWrapperState createState() => _ScreensWrapperState();
}

class _ScreensWrapperState extends State<ScreensWrapper> {
  String selectedButton;
  MQTTManager _manager = MQTTManager();
  int _selectedPage = 0;

  final _pageOptions = [
    MQTTSubscribe(),
    MQTTPublish(),
  ];

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MQTTMessagesNotifier(),
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.blueGrey,
          title: Text('Subscribe'),
          actions: <Widget>[
            Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return FlatButton.icon(
                  onPressed: (){
                    _manager.disconnect();
                  },
                  icon: Icon(Icons.arrow_back),
                  label: Text('return'),
                );
              },
            ),
          ],
        ),
        body: _pageOptions[_selectedPage],
        bottomNavigationBar: BottomNavigationBar(
          backgroundColor: Colors.blueGrey[200],
          fixedColor: Colors.blueGrey[900],
          currentIndex: _selectedPage,
          type: BottomNavigationBarType.fixed,
          onTap: (int index){
            setState((){
              _selectedPage = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.tap_and_play),
              title: Text('Subscribe'),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.publish),
              title: Text('Publish'),
            ),
          ],
        ),
      ),
    );
  }
}

, у этого класса есть changeNotifierProvider, который, как я полагаю, предоставляет снимок моего класса MQTTMessagesNotifier при notifyListeners (); Здесь содержатся два класса:

import 'dart:convert';
import 'package:mqttapp/Services/Mqtt_messages_notifier.dart';
import 'package:flutter/material.dart';
import 'package:mqttapp/Services/MQTT.dart';
import 'package:mqttapp/Services/Mqtt_messages_notifier.dart';
import 'package:mqttapp/shared/templates.dart';
import 'package:provider/provider.dart';



class MQTTPublish extends StatefulWidget {
  @override
  _MQTTPublishState createState() => _MQTTPublishState();
}

class _MQTTPublishState extends State<MQTTPublish> {
  final _formKey = GlobalKey<FormState>();
  MQTTManager _manager = MQTTManager();
  String topic = '';
  String variable = '';
  String value = '';
  String message = '';
  String error;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
      child: Form(
        key: _formKey,
        child: Column(
          children: <Widget>[
            SizedBox(height: 10.0),
            TextFormField(
              decoration: textInputTemplate.copyWith(hintText: 'Topic'),
              validator: (val) => val.isEmpty ? 'Enter a valid topic': null,
              onChanged: (val) {
                setState(() => topic = val);
              },
            ),
            SizedBox(height: 10.0),
            TextFormField(
              decoration: textInputTemplate.copyWith(hintText: 'Variable'),
              validator: (val) => val.isEmpty ? 'Enter a valid variable': null,
              onChanged: (val) {
                setState(() => variable = val);
              },
            ),
            SizedBox(height: 10.0),
            TextFormField(
              decoration: textInputTemplate.copyWith(hintText: 'Value'),
              validator: (val) => val.isEmpty ? 'Enter a valid value': null,
              onChanged: (val) {
                setState(() => value = val);
              },
            ),
            SizedBox(height: 10.0),
            Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return RaisedButton(
                  onPressed: () async {
                    if(_formKey.currentState.validate()){
                      setState(() {
                        topic = '/v1.6/devices/' + topic;
                        message = '{"$variable" : $value}';
                        _manager.publish(topic: topic, message: message);
                      });
                    }
                  },
                  color: Colors.pink[900],
                  child: Text(
                    'Publish',
                    style: TextStyle(
                      color: Colors.pink[100],
                      fontSize: 15.0,
                    ),
                  ),
                );
              },
            ),
            SizedBox(height: 10.0),
            Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return SingleChildScrollView(
                  child: Text(
                      messagesNotifier.message
                  ),
                );
              },
            ),

            Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return Text(_manager.cliente.subscriptionsManager.subscriptions.toString());
              },
            ),
          ],
        ),
      ),
    );
  }
}

import 'package:flutter/material.dart';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqttapp/Services/MQTT.dart';
import 'package:mqttapp/Services/Mqtt_messages_notifier.dart';
import 'package:mqttapp/shared/templates.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:provider/provider.dart';


class MQTTSubscribe extends StatefulWidget {
  @override
  _MQTTSubscribeState createState() => _MQTTSubscribeState();
}

class _MQTTSubscribeState extends State<MQTTSubscribe> {
  final _formKey = GlobalKey<FormState>();
  MQTTManager _manager = MQTTManager();
  String topic = '';
  String error = '';

  @override
  Widget build(BuildContext context) {
    //_manager = ModalRoute.of(context).settings.arguments;
    //MQTTMessagesNotifier messagesNotifier = Provider.of  <MQTTMessagesNotifier>(context);
    //_manager.client = messagesNotifier.client;
    return SafeArea(
      child: Container(
          padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
          child: Form(
            key: _formKey,
            child: Column(
              children: <Widget>[
                SizedBox(height: 10.0),
                TextFormField(
                  decoration: textInputTemplate.copyWith(hintText: 'Topic'),
                  validator: (val) => val.isEmpty ? 'Enter a valid topic': null,
                  onChanged: (val) {
                    setState(() => topic = val);
                  },
                ),
                SizedBox(height: 10.0),
                Consumer<MQTTMessagesNotifier>(
                  builder: (context, messagesNotifier, child){
                    _manager.cliente = messagesNotifier.client;
                    return RaisedButton(
                      onPressed: () async {
                        if(_formKey.currentState.validate()){
                          setState(() {
                            topic = '/v1.6/devices/' + topic;
                            _manager.subscription(topic: topic);
                          });
                          //Navigator.pushReplacementNamed(context, '/publish',arguments: _manager);
                        }else{
                          error='idk wtf man';
                        }
                      },
                      color: Colors.pink[900],
                      child: Text(
                        'Subscription to topics',
                        style: TextStyle(
                          color: Colors.pink[100],
                          fontSize: 15.0,
                        ),
                      ),
                    );
                  },
                ),
                SizedBox(height: 10.0),
                Text(error),
              ],
            ),
          ),
        ),
    );
  }
}

Проблема в том, что t, когда я пытаюсь отобразить эту указанную c часть кода:

Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return Text(_manager.cliente.subscriptionsManager.subscriptions.toString());
              },
            ),

На консоли появляется следующее предупреждение:

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building Consumer<MQTTMessagesNotifier>(dirty, dependencies: [_DefaultInheritedProviderScope<MQTTMessagesNotifier>]):
The getter 'subscriptionsManager' was called on null.
Receiver: null
Tried calling: subscriptionsManager

The relevant error-causing widget was: 
  Consumer<MQTTMessagesNotifier> file:///C:/Users/brand/Documents/Proyecto%20Final%20ITS/AppsFlutter/mqtt_app/lib/screens/Mqtt_screens/Mqtt_Publish.dart:97:13
When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1      _MQTTPublishState.build.<anonymous closure> (package:mqttapp/screens/Mqtt_screens/Mqtt_Publish.dart:100:46)
#2      Consumer.buildWithChild (package:provider/src/consumer.dart:175:12)
#3      SingleChildStatelessWidget.build (package:nested/nested.dart:260:41)
#4      StatelessElement.build (package:flutter/src/widgets/framework.dart:4291:28)
...
════════════════════════════════════════════════════════════════════════════════════════════════════

Которое, как я полагаю, отправлено, потому что .cliente в _manager.cliente.subscriptionsManager.subscriptions.toString() равно null, что означает, что он, вероятно, не был создан правильно (?). Я пытался увидеть, генерирует ли фактическая переменная, полученная потребителем, что-то вроде этого:

Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                print(messagesNotifier.client.clientIdentifier);
                _manager.cliente = messagesNotifier.client;
                return Text(_manager.cliente.subscriptionsManager.subscriptions.toString());
              },
            ),

Но на самом деле он ничего не печатает. На этом этапе необходимо учитывать следующее:

  1. Метод инициализации, метод соединения, метод publi sh и метод подписки работают должным образом, поскольку в отдельной версии я перемещался между экраны с использованием Navigator и передачей экземпляра MQTTManager в качестве аргументов, и он работал хорошо.

  2. Изначально проблема заключалась в том, что переменная _messageOfNotifier в классе MQTTMessagesNotifier не работала , так как я никогда не получал сообщение в этой части:

Consumer<MQTTMessagesNotifier>(
              builder: (context, messagesNotifier, child){
                _manager.cliente = messagesNotifier.client;
                return SingleChildScrollView(
                  child: Text(
                      messagesNotifier.message
                  ),
                );
              },
            ),

Я предполагаю, что проблема может быть в некоторых из этих пунктов:

  • Когда я делаю это _manager.cliente = messagesNotifier.client; в виджетах Consumer, что-то может неправильно создаваться или инициализироваться.

  • Когда я инициализирую MQTTManager _manager = MQTTManager(); в классах MQTTSubscribe, MQTTPubli sh и ScreensWrapper , Я делаю это неправильно.

  • В классе MQTTMessagesNotifier мне нужно правильно создать экземпляр или инициализировать MqttServerClient _client, что я, честно говоря, так не считаю, так как MqttServ erCliente получает в конструкторе два параметра: хост и идентификатор, которые я не могу получить, и в случае, если я сделаю рефакторинг, я буду создавать новый MqttServerClient каждый раз, когда я запускаю MQTTMessagesNotifier, и мне нужно сослаться на уже созданный в класс MQTTManager.

My pubspe c .yaml file:

name: mqttapp
description: A new Flutter application.

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  mqtt_client: ^6.2.1
  provider: ^4.0.5

dev_dependencies:
  flutter_test:
    sdk: flutter


# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #  - images/a_dot_burr.jpeg
  #  - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

Заранее спасибо, ребята! Действительно отчаянно, чтобы узнать решение: (

...