Initialize a null safe variable without a default constructor?

545

Solution 1

The default device cannot be null because of null safety.

You got this backwards. Null-safety is never the reason something can or cannot be null. You decide whether something can or cannot be null. It has been this way forever, the programmer decides if a variable sometimes is null. All null-safety does is force the programmer to share this secret arcane knowledge with their compiler, so the compiler can do it's job and warn the programmer if their logic contains mistakes.

So if you think that having no device for the start of the program (sounds reasonable), then you can decide to make it nullable. Use BluetoothDevice? as the type and it is nullable. And now your compiler can tell you all the places where you (mistakenly) assume it never is null. That's the great thing about null-safety. It's here to help you with whatever choice you make, not constrain you in your choices.

Solution 2

Make your variable nullable BluetoothDevice? device; And check if device is null later

Share:
545
Alex Nesta
Author by

Alex Nesta

I'm a Molecular Biologist working on his graduate degree. Over the next few years I hope to get a good handle on data science using R and Python.

Updated on December 06, 2022

Comments

  • Alex Nesta
    Alex Nesta over 1 year

    I'm sure this problem has been asked before, but I can't figure out how to even properly word it.

    I am trying to get Bluetooth Device data into my flutter app. The examples I have found either use non-null safe versions of dart code or they hide all of the important details.

    I am trying to build a very simple prototype from scratch so I can get a better grasp on things.

    I want to make a stateful widget that updates based on notifyListeners(). The idea is I start out with "noName bluetooth device", then once I have a device connected, I can update the object and display the name of the connected device.

    I keep running into this same roadblock and I can't get past it. I want to make a default Bluetooth Device, but the device has no default constructor. The default device cannot be null because of null safety.

    Can someone help me figure this out. There is something I know I am fundamentally misunderstanding, but I don't know where to start.

    The code below makes a ChangeNotifierProvider the parent of my BlueTesting widget that should display details of the connected Bluetooth Device (I haven't written all of the code yet).

    The BTDevices class should update the Bluetooth Device object, and notify the app to display the updated data from "the default empty device" to the new connected device.

    Thank you for your help!

    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();
      }
    }
    
    

    UPDATE:

    I tried updating the above code to set:

    BluetoothDevice device = null;
    

    A value of type 'Null' can't be assigned to a variable of type 'BluetoothDevice'. Try changing the type of the variable, or casting the right-hand type to 'BluetoothDevice'.dartinvalid_assignment

    here is the information on the BluetoothDevice definition:

    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 }
    
    • jamesdlin
      jamesdlin over 2 years
      "The default device cannot be null because of null safety." Null-safety does not mean never using null; it means explicitly identifying, tracking, and checking variables that could be null. There's nothing wrong with making a variable nullable and checking if it's null later. You alternatively could declare device as late if you can logically guarantee that device will never be accessed before calling setDevice, but that's often error-prone since there's no way for callers to dynamically check if a late variable has been initialized.
    • Alex Nesta
      Alex Nesta over 2 years
      Thanks for the information. I updated the error for when I set device to null. In my case now, I want to display a "default value" for device, and in the future I would like to update that value. So, setting it as a late variable would be error prone. Maybe the proper approach is to declare device as late and then check if it is not null before displaying it, otherwise don't display. That could work but it's not ideal I think.
  • Alex Nesta
    Alex Nesta over 2 years
    Thank you! Clear solution and explanation. Also, it works!