Skip to content

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nt4f04uNd committed Mar 7, 2022
1 parent d4151d3 commit b0e0585
Show file tree
Hide file tree
Showing 18 changed files with 227 additions and 350 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.2.0

* Fixed CancellationSignal sometimes throw MissingPluginException on cancel
* Made method channels non-serial
* Fixed loadThumbnail return type
* Deleted autoCloseScope and Closeable, because it isn't possible to properly implement them in Dart - now you should manually close all cursors

## 0.1.0

* Fixed that NativeCursor "get" methods were throwing with null values
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public class MyAndroidContentProvider extends AndroidContentProvider {
android:writePermission="com.example.myapp.permission.WRITE" />
```

4. Sublcass `AndroidContentProvider` in Dart code and override needed methods
4. Subclass `AndroidContentProvider` in Dart code and override needed methods

```dart
import 'package:android_content_provider/android_content_provider.dart';
Expand Down
5 changes: 5 additions & 0 deletions android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,8 @@ abstract class AndroidContentProvider : ContentProvider(), LifecycleOwner, Utils
engine.dartExecutor.binaryMessenger,
"${AndroidContentProviderPlugin.channelPrefix}/ContentProvider/$authority",
AndroidContentProviderPlugin.pluginMethodCodec,
engine.dartExecutor.binaryMessenger.makeBackgroundTaskQueue()))
// TODO: uncomment when this is on stable
// BinaryMessenger.TaskQueueOptions().setIsSerial(false))))
engine.dartExecutor.binaryMessenger.makeBackgroundTaskQueue(
BinaryMessenger.TaskQueueOptions().setIsSerial(false))))
@Suppress("UNCHECKED_CAST")
methodChannel.methodChannel.setMethodCallHandler { call, result ->
try {
Expand Down Expand Up @@ -182,14 +181,14 @@ abstract class AndroidContentProvider : ContentProvider(), LifecycleOwner, Utils
/** Calls [MethodChannel.invokeMethod], blocking the caller thread. */
@SuppressWarnings("WeakerAccess")
protected fun invokeMethod(method: String, arguments: Any?): Any? {
return methodChannel!!.invokeMethod(method, arguments)
return methodChannel.invokeMethod(method, arguments)
}

/** Calls [MethodChannel.invokeMethod], not blocking the caller thread. */
@SuppressWarnings("WeakerAccess")
protected fun asyncInvokeMethod(method: String, arguments: Any?) {
Handler(Looper.getMainLooper()).post {
methodChannel!!.methodChannel.invokeMethod(method, arguments)
methodChannel.methodChannel.invokeMethod(method, arguments)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ internal class AndroidContentResolver(
messenger,
"${AndroidContentProviderPlugin.channelPrefix}/ContentResolver",
AndroidContentProviderPlugin.pluginMethodCodec,
messenger.makeBackgroundTaskQueue())
// TODO: uncomment when this is on stable
// BinaryMessenger.TaskQueueOptions().setIsSerial(false)))
messenger.makeBackgroundTaskQueue(
BinaryMessenger.TaskQueueOptions().setIsSerial(false)))

init {
methodChannel.setMethodCallHandler(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ class InteroperableCancellationSignal private constructor(
pendingCancel = true
} else {
pendingCancel = false
// Dart already know the signal is cancelled, because it was the
// Dart already knows the signal is cancelled, because it was the
// initiator of the cancel.
if (!cancelledFromDart) {
methodChannel?.setMethodCallHandler(null)
Handler(Looper.getMainLooper()).post {
try {
methodChannel?.invokeMethod("cancel", null)
} catch (e: Exception) {
// Ignore because the signal might be already disposed on Dart side
} finally {
destroy()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ abstract class Interoperable<T : Interoperable.InteroperableChannel>(
AndroidContentProviderPlugin.TrackingMapKeys.BACKGROUND_TASK_QUEUES.value)
var taskQueue = taskQueueMap[classId]
if (taskQueue == null) {
taskQueue = messenger.makeBackgroundTaskQueue()
// TODO: uncomment when this is on stable
// BinaryMessenger.TaskQueueOptions().setIsSerial(false))
taskQueue = messenger.makeBackgroundTaskQueue(
BinaryMessenger.TaskQueueOptions().setIsSerial(false))
taskQueueMap[classId] = taskQueue
}
return taskQueue!!
Expand Down
29 changes: 15 additions & 14 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class MyApp extends StatefulWidget {
}

class _MyAppState extends State<MyApp> {
List<List<Object>>? songs;
List<List<Object?>>? songs;

@override
void initState() {
Expand All @@ -32,16 +32,15 @@ class _MyAppState extends State<MyApp> {
}

void fetch() async {
await autoCloseScope(() async {
final cursor = await AndroidContentResolver.instance.query(
// MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
uri: 'content://media/external/audio/media',
projection: ['_id', 'title'],
selection: null,
selectionArgs: null,
sortOrder: null,
);

final cursor = await AndroidContentResolver.instance.query(
// MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
uri: 'content://media/external/audio/media',
projection: ['_id', 'title'],
selection: null,
selectionArgs: null,
sortOrder: null,
);
try {
final end = (await cursor!.batchedGet().getCount().commit()).first as int;
final batch = cursor.batchedGet().getInt(0).getString(1);

Expand All @@ -68,7 +67,9 @@ class _MyAppState extends State<MyApp> {

// prints true
print(slowSongs.toString() == songs.toString());
});
} finally {
cursor?.close();
}
}

Future<void> measure(Function callback) async {
Expand All @@ -91,8 +92,8 @@ class _MyAppState extends State<MyApp> {
: ListView.builder(
itemExtent: 50,
itemCount: songs!.length,
itemBuilder: (context, index) => Text(
songs![index].last as String,
itemBuilder: (context, index) => ListTile(
title: Text(songs![index].last as String),
),
),
),
Expand Down
146 changes: 72 additions & 74 deletions integration_test/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -546,92 +546,90 @@ Future<void> main() async {
for (final row in rows) {
verifyRow(row);
}

cursor.close();
}

await autoCloseScope(() async {
await testCursor(await AndroidContentResolver.instance.query(
uri: providerUri,
projection: Stubs.stringList,
selection: Stubs.string,
selectionArgs: Stubs.stringList,
sortOrder: Stubs.string,
));
await testCursor(await AndroidContentResolver.instance.query(
uri: providerUri,
projection: Stubs.stringList,
selection: Stubs.string,
selectionArgs: Stubs.stringList,
sortOrder: Stubs.string,
));

await testCursor(await AndroidContentResolver.instance.queryWithSignal(
uri: providerUri,
projection: Stubs.stringList,
selection: Stubs.string,
selectionArgs: Stubs.stringList,
sortOrder: Stubs.string,
cancellationSignal: CancellationSignal()..cancel(),
));

await testCursor(await AndroidContentResolver.instance.queryWithExtras(
uri: queryWithExtrasTest,
projection: Stubs.stringList,
queryArgs: Stubs.sql_extras,
cancellationSignal: CancellationSignal()..cancel(),
));
});
await testCursor(await AndroidContentResolver.instance.queryWithSignal(
uri: providerUri,
projection: Stubs.stringList,
selection: Stubs.string,
selectionArgs: Stubs.stringList,
sortOrder: Stubs.string,
cancellationSignal: CancellationSignal()..cancel(),
));

await testCursor(await AndroidContentResolver.instance.queryWithExtras(
uri: queryWithExtrasTest,
projection: Stubs.stringList,
queryArgs: Stubs.sql_extras,
cancellationSignal: CancellationSignal()..cancel(),
));
});

test("NativeCursor - can get null values", () async {
await autoCloseScope(() async {
final cursor = await AndroidContentResolver.instance.query(
uri: nullableCursorTest,
projection: null,
selection: null,
selectionArgs: null,
sortOrder: null,
);
final batch = cursor!.batchedGet()
..getBytes(0)
..getString(1)
..getShort(2)
..getInt(3)
..getLong(4)
..getFloat(5)
..getDouble(6)
..isNull(7);
while (await cursor.moveToNext()) {
final row = await batch.commit();
expect(row, Stubs.query_rowDataAllNullsToExpect);
}
final rows = await batch.commitRange(0, 1);
for (final row in rows) {
expect(row, Stubs.query_rowDataAllNullsToExpect);
}
});
final cursor = await AndroidContentResolver.instance.query(
uri: nullableCursorTest,
projection: null,
selection: null,
selectionArgs: null,
sortOrder: null,
);
final batch = cursor!.batchedGet()
..getBytes(0)
..getString(1)
..getShort(2)
..getInt(3)
..getLong(4)
..getFloat(5)
..getDouble(6)
..isNull(7);
while (await cursor.moveToNext()) {
final row = await batch.commit();
expect(row, Stubs.query_rowDataAllNullsToExpect);
}
final rows = await batch.commitRange(0, 1);
for (final row in rows) {
expect(row, Stubs.query_rowDataAllNullsToExpect);
}
cursor.close();
});

test("NativeCursor - getColumnIndexOrThrow", () async {
await autoCloseScope(() async {
final cursor = await AndroidContentResolver.instance.query(
uri: nullableCursorTest,
projection: null,
selection: null,
selectionArgs: null,
sortOrder: null,
);
final cursor = await AndroidContentResolver.instance.query(
uri: nullableCursorTest,
projection: null,
selection: null,
selectionArgs: null,
sortOrder: null,
);

final goodBatch = cursor!.batchedGet()
..getColumnIndexOrThrow(Stubs.query_columnNames.first);
final badBatch = cursor.batchedGet()
..getColumnIndexOrThrow('missing-column');
final goodBatch = cursor!.batchedGet()
..getColumnIndexOrThrow(Stubs.query_columnNames.first);
final badBatch = cursor.batchedGet()
..getColumnIndexOrThrow('missing-column');

final throwsMissingColumn = throwsA(isA<PlatformException>().having(
(e) => e.details,
'details',
contains("column 'missing-column' does not exist"),
));
final throwsMissingColumn = throwsA(isA<PlatformException>().having(
(e) => e.details,
'details',
contains("column 'missing-column' does not exist"),
));

while (await cursor.moveToNext()) {
expect(() => goodBatch.commit(), returnsNormally);
expect(() => badBatch.commit(), throwsMissingColumn);
}
expect(() => goodBatch.commitRange(0, 1), returnsNormally);
expect(() => badBatch.commitRange(0, 1), throwsMissingColumn);
});
while (await cursor.moveToNext()) {
expect(() => goodBatch.commit(), returnsNormally);
expect(() => badBatch.commit(), throwsMissingColumn);
}
expect(() => goodBatch.commitRange(0, 1), returnsNormally);
expect(() => badBatch.commitRange(0, 1), throwsMissingColumn);
cursor.close();
});

test("refresh", () async {
Expand Down
12 changes: 10 additions & 2 deletions lib/src/android_content_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ part of android_content_provider;
///
/// Doesn't expose a subset of methods related to sync API and URI permissions,
/// it seems like they would fit separate packages.
///
/// Some methods are not compatible with older Android versions.
/// They are marked with 1 of these annotations:
///
/// * [RequiresApiOrNoop]
/// * [RequiresApiOrThrows]
/// * [RequiresApiOr]
///
class AndroidContentResolver {
/// Creates a communication interface with native Android ContentResolver.
///
Expand Down Expand Up @@ -294,7 +302,7 @@ class AndroidContentResolver {
await _methodChannel.invokeMapMethod<String, Object?>('getTypeInfo', {
'mimeType': mimeType,
});
return MimeTypeInfo.fromMap(result!);
return result == null ? null : MimeTypeInfo.fromMap(result);
}

/// insert(uri: Uri, values: ContentValues?): Uri?
Expand Down Expand Up @@ -324,7 +332,7 @@ class AndroidContentResolver {
/// loadThumbnail(uri: Uri, size: Size, signal: CancellationSignal?): Bitmap
/// https://developer.android.com/reference/kotlin/android/content/ContentResolver#loadthumbnail
@RequiresApiOrThrows(29)
Future<Uint8List?> loadThumbnail({
Future<Uint8List> loadThumbnail({
required String uri,
required int width,
required int height,
Expand Down
9 changes: 7 additions & 2 deletions lib/src/cancellation_signal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ class CancellationSignal extends ReceivedCancellationSignal {
_cancelled = true;
_cancelListener?.call();
_initCompleter.operation.then((_) async {
_methodChannel.setMethodCallHandler(null);
await _methodChannel.invokeMethod<void>('cancel', {'id': id});
try {
await _methodChannel.invokeMethod<void>('cancel', {'id': id});
} catch (e) {
// Ignore because the signal might be already disposed on native side
} finally {
dispose();
}
});
}
}
Expand Down
Loading

0 comments on commit b0e0585

Please sign in to comment.