【问题标题】:Initialize a null safe variable without a default constructor?在没有默认构造函数的情况下初始化一个空安全变量?
【发布时间】:2021-12-24 05:56:51
【问题描述】:

我敢肯定这个问题以前有人问过,但我什至不知道如何正确表达它。

我正在尝试将蓝牙设备数据导入我的颤振应用程序。我发现的示例要么使用非空安全版本的 dart 代码,要么隐藏所有重要细节。

我正在尝试从头开始构建一个非常简单的原型,以便更好地掌握事物。

我想制作一个基于 notifyListeners() 进行更新的有状态小部件。我的想法是从“noName 蓝牙设备”开始,然后一旦连接了设备,我就可以更新对象并显示连接设备的名称。

我一直遇到同样的障碍,我无法克服它。我想制作一个默认的蓝牙设备,但该设备没有默认构造函数。由于 null 安全,默认设备不能为 null。

谁能帮我解决这个问题。有些事情我知道我从根本上误解了,但我不知道从哪里开始。

下面的代码使 ChangeNotifierProvider 成为我的 BlueTesting 小部件的父级,它应该显示连接的蓝牙设备的详细信息(我还没有编写所有代码)。

BTDevices 类应该更新蓝牙设备对象,并通知应用程序将更新的数据从“默认空设备”显示到新连接的设备。

感谢您的帮助!

import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:provider/provider.dart';

void main() => runApp(
    ChangeNotifierProvider(create: (_) => BTDevices(), child: BlueTesting()));

class BTDevices extends ChangeNotifier {
  BluetoothDevice device = BluetoothDevice();
  void setDevice(BluetoothDevice newDevice) {
    device = newDevice;
    notifyListeners();
  }
}

更新:

我尝试更新上面的代码来设置:

BluetoothDevice device = null;

“Null”类型的值不能分配给“BluetoothDevice”类型的变量。 尝试更改变量的类型,或将右侧类型转换为 'BluetoothDevice'.dartinvalid_assignment

这里是关于蓝牙设备定义的信息:

part of flutter_blue;

class BluetoothDevice {
  final DeviceIdentifier id;
  final String name;
  final BluetoothDeviceType type;

  BluetoothDevice.fromProto(protos.BluetoothDevice p)
      : id = new DeviceIdentifier(p.remoteId),
        name = p.name,
        type = BluetoothDeviceType.values[p.type.value];

  BehaviorSubject<bool> _isDiscoveringServices = BehaviorSubject.seeded(false);
  Stream<bool> get isDiscoveringServices => _isDiscoveringServices.stream;

  /// Establishes a connection to the Bluetooth Device.
  Future<void> connect({
    Duration? timeout,
    bool autoConnect = true,
  }) async {
    var request = protos.ConnectRequest.create()
      ..remoteId = id.toString()
      ..androidAutoConnect = autoConnect;

    Timer? timer;
    if (timeout != null) {
      timer = Timer(timeout, () {
        disconnect();
        throw TimeoutException('Failed to connect in time.', timeout);
      });
    }

    await FlutterBlue.instance._channel
        .invokeMethod('connect', request.writeToBuffer());

    await state.firstWhere((s) => s == BluetoothDeviceState.connected);

    timer?.cancel();

    return;
  }

  /// Cancels connection to the Bluetooth Device
  Future disconnect() =>
      FlutterBlue.instance._channel.invokeMethod('disconnect', id.toString());

  BehaviorSubject<List<BluetoothService>> _services =
      BehaviorSubject.seeded([]);

  /// Discovers services offered by the remote device as well as their characteristics and descriptors
  Future<List<BluetoothService>> discoverServices() async {
    final s = await state.first;
    if (s != BluetoothDeviceState.connected) {
      return Future.error(new Exception(
          'Cannot discoverServices while device is not connected. State == $s'));
    }
    var response = FlutterBlue.instance._methodStream
        .where((m) => m.method == "DiscoverServicesResult")
        .map((m) => m.arguments)
        .map((buffer) => new protos.DiscoverServicesResult.fromBuffer(buffer))
        .where((p) => p.remoteId == id.toString())
        .map((p) => p.services)
        .map((s) => s.map((p) => new BluetoothService.fromProto(p)).toList())
        .first
        .then((list) {
      _services.add(list);
      _isDiscoveringServices.add(false);
      return list;
    });

    await FlutterBlue.instance._channel
        .invokeMethod('discoverServices', id.toString());

    _isDiscoveringServices.add(true);

    return response;
  }

  /// Returns a list of Bluetooth GATT services offered by the remote device
  /// This function requires that discoverServices has been completed for this device
  Stream<List<BluetoothService>> get services async* {
    yield await FlutterBlue.instance._channel
        .invokeMethod('services', id.toString())
        .then((buffer) =>
            new protos.DiscoverServicesResult.fromBuffer(buffer).services)
        .then((i) => i.map((s) => new BluetoothService.fromProto(s)).toList());
    yield* _services.stream;
  }

  /// The current connection state of the device
  Stream<BluetoothDeviceState> get state async* {
    yield await FlutterBlue.instance._channel
        .invokeMethod('deviceState', id.toString())
        .then((buffer) => new protos.DeviceStateResponse.fromBuffer(buffer))
        .then((p) => BluetoothDeviceState.values[p.state.value]);

    yield* FlutterBlue.instance._methodStream
        .where((m) => m.method == "DeviceState")
        .map((m) => m.arguments)
        .map((buffer) => new protos.DeviceStateResponse.fromBuffer(buffer))
        .where((p) => p.remoteId == id.toString())
        .map((p) => BluetoothDeviceState.values[p.state.value]);
  }

  /// The MTU size in bytes
  Stream<int> get mtu async* {
    yield await FlutterBlue.instance._channel
        .invokeMethod('mtu', id.toString())
        .then((buffer) => new protos.MtuSizeResponse.fromBuffer(buffer))
        .then((p) => p.mtu);

    yield* FlutterBlue.instance._methodStream
        .where((m) => m.method == "MtuSize")
        .map((m) => m.arguments)
        .map((buffer) => new protos.MtuSizeResponse.fromBuffer(buffer))
        .where((p) => p.remoteId == id.toString())
        .map((p) => p.mtu);
  }

  /// Request to change the MTU Size
  /// Throws error if request did not complete successfully
  Future<void> requestMtu(int desiredMtu) async {
    var request = protos.MtuSizeRequest.create()
      ..remoteId = id.toString()
      ..mtu = desiredMtu;

    return FlutterBlue.instance._channel
        .invokeMethod('requestMtu', request.writeToBuffer());
  }

  /// Indicates whether the Bluetooth Device can send a write without response
  Future<bool> get canSendWriteWithoutResponse =>
      new Future.error(new UnimplementedError());

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BluetoothDevice &&
          runtimeType == other.runtimeType &&
          id == other.id;

  @override
  int get hashCode => id.hashCode;

  @override
  String toString() {
    return 'BluetoothDevice{id: $id, name: $name, type: $type, isDiscoveringServices: ${_isDiscoveringServices.value}, _services: ${_services.value}';
  }
}

enum BluetoothDeviceType { unknown, classic, le, dual }

enum BluetoothDeviceState { disconnected, connecting, connected, disconnecting }

【问题讨论】:

  • "默认设备不能为 null,因为 null 安全。"空安全并不意味着从不使用null;这意味着明确识别、跟踪和检查可能是null 的变量。使变量可以为空并稍后检查它是否为null 没有任何问题。您也可以将device 声明为late,前提是您可以从逻辑上保证在调用setDevice 之前永远不会访问device,但这通常容易出错,因为调用者无法动态检查 late 变量是否已初始化。
  • 感谢您的信息。我更新了将设备设置为空时的错误。现在就我而言,我想为设备显示一个“默认值”,并且将来我想更新该值。因此,将其设置为后期变量将容易出错。也许正确的方法是将设备声明为迟,然后在显示之前检查它是否不为空,否则不显示。这可能行得通,但我认为这并不理想。

标签: flutter dart android-bluetooth flutter-provider


【解决方案1】:

由于 null 安全,默认设备不能为 null。

你把这个弄反了。空安全永远不是某事可以或不能为空的原因。 您决定某事是否可以为空。一直以来都是这样,程序员决定变量是否有时为空。所有 null-safety 都会强制程序员与他们的编译器分享这个神秘的秘密知识,这样编译器就可以完成它的工作,并在他们的逻辑包含错误时警告程序员。

因此,如果您认为没有用于启动程序的设备(听起来很合理),那么您可以决定将其设为可空。使用BluetoothDevice? 作为类型,它可以为空。现在你的编译器可以告诉你所有你(错误地)假设它永远不会为空的地方。这就是零安全性的伟大之处。它在这里帮助你做出任何选择,而不是限制你的选择。

【讨论】:

  • 谢谢!清晰的解决方案和解释。而且,它有效!
【解决方案2】:

使你的变量可以为空 蓝牙设备?设备; 并稍后检查设备是否为空

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多