[path_provider] Fix handling of null application ID (#4606)

The lookup of application ID was handling `null` (Dart null), but not
`nullptr` (Dart representation of a C null pointer), so was throwing an
exception in applications without an application ID.

This includes the `shared_preferences_linux` example application, so
this fixes tree breakage.
diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md
index f227570..cfea570 100644
--- a/packages/path_provider/path_provider_linux/CHANGELOG.md
+++ b/packages/path_provider/path_provider_linux/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.1.4
+
+* Fixes `getApplicationSupportPath` handling of applications where the
+  application ID is not set.
+
 ## 2.1.3
 
 * Change getApplicationSupportPath from using executable name to application ID (if provided).
diff --git a/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart b/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart
index f6d25bb..d2e60fb 100644
--- a/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart
+++ b/packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart
@@ -4,6 +4,7 @@
 
 import 'dart:ffi';
 import 'package:ffi/ffi.dart';
+import 'package:meta/meta.dart';
 
 // GApplication* g_application_get_default();
 typedef _GApplicationGetDefaultC = IntPtr Function();
@@ -13,30 +14,65 @@
 typedef _GApplicationGetApplicationIdC = Pointer<Utf8> Function(IntPtr);
 typedef _GApplicationGetApplicationIdDart = Pointer<Utf8> Function(int);
 
+/// Interface for interacting with libgio.
+@visibleForTesting
+class GioUtils {
+  /// Creates a default instance that uses the real libgio.
+  GioUtils() {
+    try {
+      _gio = DynamicLibrary.open('libgio-2.0.so');
+    } on ArgumentError {
+      _gio = null;
+    }
+  }
+
+  DynamicLibrary? _gio;
+
+  /// True if libgio was opened successfully.
+  bool get libraryIsPresent => _gio != null;
+
+  /// Wraps `g_application_get_default`.
+  int gApplicationGetDefault() {
+    if (_gio == null) {
+      return 0;
+    }
+    final _GApplicationGetDefaultDart getDefault = _gio!
+        .lookupFunction<_GApplicationGetDefaultC, _GApplicationGetDefaultDart>(
+            'g_application_get_default');
+    return getDefault();
+  }
+
+  /// Wraps g_application_get_application_id.
+  Pointer<Utf8> gApplicationGetApplicationId(int app) {
+    if (_gio == null) {
+      return nullptr;
+    }
+    final _GApplicationGetApplicationIdDart gApplicationGetApplicationId = _gio!
+        .lookupFunction<_GApplicationGetApplicationIdC,
+                _GApplicationGetApplicationIdDart>(
+            'g_application_get_application_id');
+    return gApplicationGetApplicationId(app);
+  }
+}
+
+/// Allows overriding the default GioUtils instance with a fake for testing.
+@visibleForTesting
+GioUtils? gioUtilsOverride;
+
 /// Gets the application ID for this app.
 String? getApplicationId() {
-  DynamicLibrary gio;
-  try {
-    gio = DynamicLibrary.open('libgio-2.0.so');
-  } on ArgumentError {
+  final GioUtils gio = gioUtilsOverride ?? GioUtils();
+  if (!gio.libraryIsPresent) {
     return null;
   }
-  final _GApplicationGetDefaultDart gApplicationGetDefault =
-      gio.lookupFunction<_GApplicationGetDefaultC, _GApplicationGetDefaultDart>(
-          'g_application_get_default');
-  final int app = gApplicationGetDefault();
+
+  final int app = gio.gApplicationGetDefault();
   if (app == 0) {
     return null;
   }
-
-  final _GApplicationGetApplicationIdDart gApplicationGetApplicationId =
-      gio.lookupFunction<_GApplicationGetApplicationIdC,
-              _GApplicationGetApplicationIdDart>(
-          'g_application_get_application_id');
-  final Pointer<Utf8> appId = gApplicationGetApplicationId(app);
-  if (appId == null) {
+  final Pointer<Utf8> appId = gio.gApplicationGetApplicationId(app);
+  if (appId == null || appId == nullptr) {
     return null;
   }
-
   return appId.toDartString();
 }
diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml
index 25d1af9..ec1d8d8 100644
--- a/packages/path_provider/path_provider_linux/pubspec.yaml
+++ b/packages/path_provider/path_provider_linux/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Linux implementation of the path_provider plugin
 repository: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.1.3
+version: 2.1.4
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -19,6 +19,7 @@
   ffi: ^1.1.2
   flutter:
     sdk: flutter
+  meta: ^1.3.0
   path: ^1.8.0
   path_provider_platform_interface: ^2.0.0
   xdg_directories: ^0.2.0
diff --git a/packages/path_provider/path_provider_linux/test/get_application_id_test.dart b/packages/path_provider/path_provider_linux/test/get_application_id_test.dart
new file mode 100644
index 0000000..d9eb516
--- /dev/null
+++ b/packages/path_provider/path_provider_linux/test/get_application_id_test.dart
@@ -0,0 +1,62 @@
+// Copyright 2013 The Flutter 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:ffi';
+
+import 'package:ffi/ffi.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path_provider_linux/src/get_application_id_real.dart';
+
+class _FakeGioUtils implements GioUtils {
+  int? application;
+  Pointer<Utf8>? applicationId;
+
+  @override
+  bool libraryIsPresent = false;
+
+  @override
+  int gApplicationGetDefault() => application!;
+
+  @override
+  Pointer<Utf8> gApplicationGetApplicationId(int app) => applicationId!;
+}
+
+void main() {
+  late _FakeGioUtils fakeGio;
+
+  setUp(() {
+    fakeGio = _FakeGioUtils();
+    gioUtilsOverride = fakeGio;
+  });
+
+  tearDown(() {
+    gioUtilsOverride = null;
+  });
+
+  test('returns null if libgio is not available', () {
+    expect(getApplicationId(), null);
+  });
+
+  test('returns null if g_paplication_get_default returns 0', () {
+    fakeGio.libraryIsPresent = true;
+    fakeGio.application = 0;
+    expect(getApplicationId(), null);
+  });
+
+  test('returns null if g_application_get_application_id returns nullptr', () {
+    fakeGio.libraryIsPresent = true;
+    fakeGio.application = 1;
+    fakeGio.applicationId = nullptr;
+    expect(getApplicationId(), null);
+  });
+
+  test('returns value if g_application_get_application_id returns a value', () {
+    fakeGio.libraryIsPresent = true;
+    fakeGio.application = 1;
+    const String id = 'foo';
+    final Pointer<Utf8> idPtr = id.toNativeUtf8();
+    fakeGio.applicationId = idPtr;
+    expect(getApplicationId(), id);
+    calloc.free(idPtr);
+  });
+}