[device_info_platform_interface] handle null value from method channel (#3609)
diff --git a/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart b/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart
index 808b7ad..2dd41dc 100644
--- a/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart
+++ b/packages/device_info/device_info_platform_interface/lib/device_info_platform_interface.dart
@@ -7,10 +7,8 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'method_channel/method_channel_device_info.dart';
-
import 'model/android_device_info.dart';
import 'model/ios_device_info.dart';
-
export 'model/android_device_info.dart';
export 'model/ios_device_info.dart';
diff --git a/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart b/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart
index 7bd02e9..331f718 100644
--- a/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart
+++ b/packages/device_info/device_info_platform_interface/lib/method_channel/method_channel_device_info.dart
@@ -1,8 +1,11 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
-
import 'package:device_info_platform_interface/device_info_platform_interface.dart';
/// An implementation of [DeviceInfoPlatform] that uses method channels.
@@ -13,16 +16,15 @@
// Method channel for Android devices
Future<AndroidDeviceInfo> androidInfo() async {
- return AndroidDeviceInfo.fromMap(
- (await channel.invokeMethod('getAndroidDeviceInfo'))
- .cast<String, dynamic>(),
- );
+ return AndroidDeviceInfo.fromMap((await channel
+ .invokeMapMethod<String, dynamic>('getAndroidDeviceInfo')) ??
+ <String, dynamic>{});
}
// Method channel for iOS devices
Future<IosDeviceInfo> iosInfo() async {
return IosDeviceInfo.fromMap(
- (await channel.invokeMethod('getIosDeviceInfo')).cast<String, dynamic>(),
- );
+ (await channel.invokeMapMethod<String, dynamic>('getIosDeviceInfo')) ??
+ <String, dynamic>{});
}
}
diff --git a/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart
index 4fb940c..c5210ab 100644
--- a/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart
+++ b/packages/device_info/device_info_platform_interface/lib/model/android_device_info.dart
@@ -38,39 +38,63 @@
final AndroidBuildVersion version;
/// The name of the underlying board, like "goldfish".
+ ///
+ /// The value is an empty String if it is not available.
final String board;
/// The system bootloader version number.
+ ///
+ /// The value is an empty String if it is not available.
final String bootloader;
/// The consumer-visible brand with which the product/hardware will be associated, if any.
+ ///
+ /// The value is an empty String if it is not available.
final String brand;
/// The name of the industrial design.
+ ///
+ /// The value is an empty String if it is not available.
final String device;
/// A build ID string meant for displaying to the user.
+ ///
+ /// The value is an empty String if it is not available.
final String display;
/// A string that uniquely identifies this build.
+ ///
+ /// The value is an empty String if it is not available.
final String fingerprint;
/// The name of the hardware (from the kernel command line or /proc).
+ ///
+ /// The value is an empty String if it is not available.
final String hardware;
/// Hostname.
+ ///
+ /// The value is an empty String if it is not available.
final String host;
/// Either a changelist number, or a label like "M4-rc20".
+ ///
+ /// The value is an empty String if it is not available.
final String id;
/// The manufacturer of the product/hardware.
+ ///
+ /// The value is an empty String if it is not available.
final String manufacturer;
/// The end-user-visible name for the end product.
+ ///
+ /// The value is an empty String if it is not available.
final String model;
/// The name of the overall product.
+ ///
+ /// The value is an empty String if it is not available.
final String product;
/// An ordered list of 32 bit ABIs supported by this device.
@@ -83,15 +107,23 @@
final List<String> supportedAbis;
/// Comma-separated tags describing the build, like "unsigned,debug".
+ ///
+ /// The value is an empty String if it is not available.
final String tags;
/// The type of build, like "user" or "eng".
+ ///
+ /// The value is an empty String if it is not available.
final String type;
- /// `false` if the application is running in an emulator, `true` otherwise.
+ /// The value is `true` if the application is running on a physical device.
+ ///
+ /// The value is `false` when the application is running on a emulator, or the value is unavailable.
final bool isPhysicalDevice;
/// The Android hardware device ID that is unique between the device + user and app signing.
+ ///
+ /// The value is an empty String if it is not available.
final String androidId;
/// Describes what features are available on the current device.
@@ -113,35 +145,41 @@
/// Deserializes from the message received from [_kChannel].
static AndroidDeviceInfo fromMap(Map<String, dynamic> map) {
return AndroidDeviceInfo(
- version:
- AndroidBuildVersion._fromMap(map['version']!.cast<String, dynamic>()),
- board: map['board']!,
- bootloader: map['bootloader']!,
- brand: map['brand']!,
- device: map['device']!,
- display: map['display']!,
- fingerprint: map['fingerprint']!,
- hardware: map['hardware']!,
- host: map['host']!,
- id: map['id']!,
- manufacturer: map['manufacturer']!,
- model: map['model']!,
- product: map['product']!,
- supported32BitAbis: _fromList(map['supported32BitAbis']!),
- supported64BitAbis: _fromList(map['supported64BitAbis']!),
- supportedAbis: _fromList(map['supportedAbis']!),
- tags: map['tags']!,
- type: map['type']!,
- isPhysicalDevice: map['isPhysicalDevice']!,
- androidId: map['androidId']!,
- systemFeatures: _fromList(map['systemFeatures']!),
+ version: AndroidBuildVersion._fromMap(map['version'] != null
+ ? map['version'].cast<String, dynamic>()
+ : <String, dynamic>{}),
+ board: map['board'] ?? '',
+ bootloader: map['bootloader'] ?? '',
+ brand: map['brand'] ?? '',
+ device: map['device'] ?? '',
+ display: map['display'] ?? '',
+ fingerprint: map['fingerprint'] ?? '',
+ hardware: map['hardware'] ?? '',
+ host: map['host'] ?? '',
+ id: map['id'] ?? '',
+ manufacturer: map['manufacturer'] ?? '',
+ model: map['model'] ?? '',
+ product: map['product'] ?? '',
+ supported32BitAbis: _fromList(map['supported32BitAbis']),
+ supported64BitAbis: _fromList(map['supported64BitAbis']),
+ supportedAbis: _fromList(map['supportedAbis']),
+ tags: map['tags'] ?? '',
+ type: map['type'] ?? '',
+ isPhysicalDevice: map['isPhysicalDevice'] ?? false,
+ androidId: map['androidId'] ?? '',
+ systemFeatures: _fromList(map['systemFeatures']),
);
}
/// Deserializes message as List<String>
static List<String> _fromList(dynamic message) {
- final List<dynamic> list = message;
- return List<String>.from(list);
+ if (message == null) {
+ return <String>[];
+ }
+ assert(message is List<dynamic>);
+ final List<dynamic> list = List<dynamic>.from(message)
+ ..removeWhere((value) => value == null);
+ return list.cast<String>();
}
}
@@ -173,17 +211,25 @@
final String? securityPatch;
/// The current development codename, or the string "REL" if this is a release build.
+ ///
+ /// The value is an empty String if it is not available.
final String codename;
/// The internal value used by the underlying source control to represent this build.
+ ///
+ /// The value is an empty String if it is not available.
final String incremental;
/// The user-visible version string.
+ ///
+ /// The value is an empty String if it is not available.
final String release;
/// The user-visible SDK version of the framework.
///
/// Possible values are defined in: https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
+ ///
+ /// The value is -1 if it is unavailable.
final int sdkInt;
/// Deserializes from the map message received from [_kChannel].
@@ -192,10 +238,10 @@
baseOS: map['baseOS'],
previewSdkInt: map['previewSdkInt'],
securityPatch: map['securityPatch'],
- codename: map['codename']!,
- incremental: map['incremental']!,
- release: map['release']!,
- sdkInt: map['sdkInt']!,
+ codename: map['codename'] ?? '',
+ incremental: map['incremental'] ?? '',
+ release: map['release'] ?? '',
+ sdkInt: map['sdkInt'] ?? -1,
);
}
}
diff --git a/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart
index eb6e587..20ec836 100644
--- a/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart
+++ b/packages/device_info/device_info_platform_interface/lib/model/ios_device_info.dart
@@ -19,40 +19,60 @@
});
/// Device name.
+ ///
+ /// The value is an empty String if it is not available.
final String name;
/// The name of the current operating system.
+ ///
+ /// The value is an empty String if it is not available.
final String systemName;
/// The current operating system version.
+ ///
+ /// The value is an empty String if it is not available.
final String systemVersion;
/// Device model.
+ ///
+ /// The value is an empty String if it is not available.
final String model;
/// Localized name of the device model.
+ ///
+ /// The value is an empty String if it is not available.
final String localizedModel;
/// Unique UUID value identifying the current device.
+ ///
+ /// The value is an empty String if it is not available.
final String identifierForVendor;
- /// `false` if the application is running in a simulator, `true` otherwise.
+ /// The value is `true` if the application is running on a physical device.
+ ///
+ /// The value is `false` when the application is running on a simulator, or the value is unavailable.
final bool isPhysicalDevice;
/// Operating system information derived from `sys/utsname.h`.
+ ///
+ /// The value is an empty String if it is not available.
final IosUtsname utsname;
/// Deserializes from the map message received from [_kChannel].
static IosDeviceInfo fromMap(Map<String, dynamic> map) {
return IosDeviceInfo(
- name: map['name']!,
- systemName: map['systemName']!,
- systemVersion: map['systemVersion']!,
- model: map['model']!,
- localizedModel: map['localizedModel']!,
- identifierForVendor: map['identifierForVendor']!,
- isPhysicalDevice: map['isPhysicalDevice'] == 'true',
- utsname: IosUtsname._fromMap(map['utsname']!.cast<String, dynamic>()),
+ name: map['name'] ?? '',
+ systemName: map['systemName'] ?? '',
+ systemVersion: map['systemVersion'] ?? '',
+ model: map['model'] ?? '',
+ localizedModel: map['localizedModel'] ?? '',
+ identifierForVendor: map['identifierForVendor'] ?? '',
+ isPhysicalDevice: map['isPhysicalDevice'] != null
+ ? map['isPhysicalDevice'] == 'true'
+ : false,
+ utsname: IosUtsname._fromMap(map['utsname'] != null
+ ? map['utsname'].cast<String, dynamic>()
+ : <String, dynamic>{}),
);
}
}
@@ -69,28 +89,38 @@
});
/// Operating system name.
+ ///
+ /// The value is an empty String if it is not available.
final String sysname;
/// Network node name.
+ ///
+ /// The value is an empty String if it is not available.
final String nodename;
/// Release level.
+ ///
+ /// The value is an empty String if it is not available.
final String release;
/// Version level.
+ ///
+ /// The value is an empty String if it is not available.
final String version;
/// Hardware type (e.g. 'iPhone7,1' for iPhone 6 Plus).
+ ///
+ /// The value is an empty String if it is not available.
final String machine;
/// Deserializes from the map message received from [_kChannel].
static IosUtsname _fromMap(Map<String, dynamic> map) {
return IosUtsname._(
- sysname: map['sysname']!,
- nodename: map['nodename']!,
- release: map['release']!,
- version: map['version']!,
- machine: map['machine']!,
+ sysname: map['sysname'] ?? '',
+ nodename: map['nodename'] ?? '',
+ release: map['release'] ?? '',
+ version: map['version'] ?? '',
+ machine: map['machine'] ?? '',
);
}
}
diff --git a/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart
index 1596385..03ff4b5 100644
--- a/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart
+++ b/packages/device_info/device_info_platform_interface/test/method_channel_device_info_test.dart
@@ -158,4 +158,220 @@
expect(result.utsname.machine, "x86_64");
});
});
+
+ group(
+ "$MethodChannelDeviceInfo handles null value in the map returned from method channel",
+ () {
+ MethodChannelDeviceInfo methodChannelDeviceInfo;
+
+ setUp(() async {
+ methodChannelDeviceInfo = MethodChannelDeviceInfo();
+
+ methodChannelDeviceInfo.channel
+ .setMockMethodCallHandler((MethodCall methodCall) async {
+ switch (methodCall.method) {
+ case 'getAndroidDeviceInfo':
+ return ({
+ "version": null,
+ "board": null,
+ "bootloader": null,
+ "brand": null,
+ "device": null,
+ "display": null,
+ "fingerprint": null,
+ "hardware": null,
+ "host": null,
+ "id": null,
+ "manufacturer": null,
+ "model": null,
+ "product": null,
+ "supported32BitAbis": null,
+ "supported64BitAbis": null,
+ "supportedAbis": null,
+ "tags": null,
+ "type": null,
+ "isPhysicalDevice": null,
+ "androidId": null,
+ "systemFeatures": null,
+ });
+ case 'getIosDeviceInfo':
+ return ({
+ "name": null,
+ "systemName": null,
+ "systemVersion": null,
+ "model": null,
+ "localizedModel": null,
+ "identifierForVendor": null,
+ "isPhysicalDevice": null,
+ "utsname": null,
+ });
+ default:
+ return null;
+ }
+ });
+ });
+
+ test("androidInfo hanels null", () async {
+ final AndroidDeviceInfo result =
+ await methodChannelDeviceInfo.androidInfo();
+
+ expect(result.version.securityPatch, null);
+ expect(result.version.sdkInt, -1);
+ expect(result.version.release, '');
+ expect(result.version.previewSdkInt, null);
+ expect(result.version.incremental, '');
+ expect(result.version.codename, '');
+ expect(result.board, '');
+ expect(result.bootloader, '');
+ expect(result.brand, '');
+ expect(result.device, '');
+ expect(result.display, '');
+ expect(result.fingerprint, '');
+ expect(result.hardware, '');
+ expect(result.host, '');
+ expect(result.id, '');
+ expect(result.manufacturer, '');
+ expect(result.model, '');
+ expect(result.product, '');
+ expect(result.supported32BitAbis, <String>[]);
+ expect(result.supported64BitAbis, <String>[]);
+ expect(result.supportedAbis, <String>[]);
+ expect(result.tags, '');
+ expect(result.type, '');
+ expect(result.isPhysicalDevice, false);
+ expect(result.androidId, '');
+ expect(result.systemFeatures, <String>[]);
+ });
+
+ test("iosInfo handles null", () async {
+ final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo();
+ expect(result.name, '');
+ expect(result.systemName, '');
+ expect(result.systemVersion, '');
+ expect(result.model, '');
+ expect(result.localizedModel, '');
+ expect(result.identifierForVendor, '');
+ expect(result.isPhysicalDevice, false);
+ expect(result.utsname.sysname, '');
+ expect(result.utsname.nodename, '');
+ expect(result.utsname.release, '');
+ expect(result.utsname.version, '');
+ expect(result.utsname.machine, '');
+ });
+ });
+
+ group("$MethodChannelDeviceInfo handles method channel returns null", () {
+ MethodChannelDeviceInfo methodChannelDeviceInfo;
+
+ setUp(() async {
+ methodChannelDeviceInfo = MethodChannelDeviceInfo();
+
+ methodChannelDeviceInfo.channel
+ .setMockMethodCallHandler((MethodCall methodCall) async {
+ switch (methodCall.method) {
+ case 'getAndroidDeviceInfo':
+ return null;
+ case 'getIosDeviceInfo':
+ return null;
+ default:
+ return null;
+ }
+ });
+ });
+
+ test("androidInfo handles null", () async {
+ final AndroidDeviceInfo result =
+ await methodChannelDeviceInfo.androidInfo();
+
+ expect(result.version.securityPatch, null);
+ expect(result.version.sdkInt, -1);
+ expect(result.version.release, '');
+ expect(result.version.previewSdkInt, null);
+ expect(result.version.incremental, '');
+ expect(result.version.codename, '');
+ expect(result.board, '');
+ expect(result.bootloader, '');
+ expect(result.brand, '');
+ expect(result.device, '');
+ expect(result.display, '');
+ expect(result.fingerprint, '');
+ expect(result.hardware, '');
+ expect(result.host, '');
+ expect(result.id, '');
+ expect(result.manufacturer, '');
+ expect(result.model, '');
+ expect(result.product, '');
+ expect(result.supported32BitAbis, <String>[]);
+ expect(result.supported64BitAbis, <String>[]);
+ expect(result.supportedAbis, <String>[]);
+ expect(result.tags, '');
+ expect(result.type, '');
+ expect(result.isPhysicalDevice, false);
+ expect(result.androidId, '');
+ expect(result.systemFeatures, <String>[]);
+ });
+
+ test("iosInfo handles null", () async {
+ final IosDeviceInfo result = await methodChannelDeviceInfo.iosInfo();
+ expect(result.name, '');
+ expect(result.systemName, '');
+ expect(result.systemVersion, '');
+ expect(result.model, '');
+ expect(result.localizedModel, '');
+ expect(result.identifierForVendor, '');
+ expect(result.isPhysicalDevice, false);
+ expect(result.utsname.sysname, '');
+ expect(result.utsname.nodename, '');
+ expect(result.utsname.release, '');
+ expect(result.utsname.version, '');
+ expect(result.utsname.machine, '');
+ });
+ });
+
+ group("$MethodChannelDeviceInfo android handles null values in list", () {
+ MethodChannelDeviceInfo methodChannelDeviceInfo;
+
+ setUp(() async {
+ methodChannelDeviceInfo = MethodChannelDeviceInfo();
+
+ methodChannelDeviceInfo.channel
+ .setMockMethodCallHandler((MethodCall methodCall) async {
+ switch (methodCall.method) {
+ case 'getAndroidDeviceInfo':
+ return ({
+ "supported32BitAbis": <String>["x86", null],
+ "supported64BitAbis": <String>["x86_64", null],
+ "supportedAbis": <String>["x86_64", "x86", null],
+ "systemFeatures": <String>[
+ "android.hardware.sensor.proximity",
+ "android.software.adoptable_storage",
+ "android.hardware.sensor.accelerometer",
+ "android.hardware.faketouch",
+ "android.software.backup",
+ "android.hardware.touchscreen",
+ null
+ ],
+ });
+ default:
+ return null;
+ }
+ });
+ });
+
+ test("androidInfo hanels null in list", () async {
+ final AndroidDeviceInfo result =
+ await methodChannelDeviceInfo.androidInfo();
+ expect(result.supported32BitAbis, <String>['x86']);
+ expect(result.supported64BitAbis, <String>['x86_64']);
+ expect(result.supportedAbis, <String>['x86_64', 'x86']);
+ expect(result.systemFeatures, <String>[
+ "android.hardware.sensor.proximity",
+ "android.software.adoptable_storage",
+ "android.hardware.sensor.accelerometer",
+ "android.hardware.faketouch",
+ "android.software.backup",
+ "android.hardware.touchscreen"
+ ]);
+ });
+ });
}