Skip to content

Commit cde5b36

Browse files
[file_selector] Switch to Kotlin Pigeon (#11479)
Replaces the Java Pigeon generator with the Kotlin Pigeon generator, and adjusts the project accordingly: - Adds Kotlin build setings to Gradle. - Updates API signatures for Kotlin/Java differences. - Adds generic Java/Kotlin compat shim to create Result objects from Java, since those haven't been added to the Pigeon generator yet. - Updates tests to use constructors instead of builders, since the Kotlin generator doesn't create builders. - Updates tests to use a Java/Kotlin compat shim to read Kotlin Result values, instead of mocking the Java Pigeon response object. This does not attempt to fix the pre-existing invalid use of null paths described in flutter/flutter#159568, but the behavior does change slightly from an IllegalStateException (which came from the Java builder) to a NullPointerException (from trying to assign to a non-null Kotlin field). Part of flutter/flutter#158287 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 5f14a80 commit cde5b36

12 files changed

Lines changed: 852 additions & 910 deletions

File tree

packages/file_selector/file_selector_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.2+6
2+
3+
* Updates internal implementation to use Kotlin Pigeon.
4+
15
## 0.5.2+5
26

37
* Updates build files from Groovy to Kotlin.

packages/file_selector/file_selector_android/android/build.gradle.kts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
13
group = "dev.flutter.packages.file_selector_android"
24
version = "1.0"
35

46
buildscript {
7+
val kotlinVersion = "2.3.0"
58
repositories {
69
google()
710
mavenCentral()
811
}
912

1013
dependencies {
1114
classpath("com.android.tools.build:gradle:8.13.1")
15+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
1216
}
1317
}
1418

@@ -21,6 +25,13 @@ allprojects {
2125

2226
plugins {
2327
id("com.android.library")
28+
id("kotlin-android")
29+
}
30+
31+
kotlin {
32+
compilerOptions {
33+
jvmTarget = JvmTarget.fromTarget(JavaVersion.VERSION_17.toString())
34+
}
2435
}
2536

2637
android {

packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPlugin.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
2828
@Override
2929
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
3030
fileSelectorApi = new FileSelectorApiImpl(binding);
31-
GeneratedFileSelectorApi.FileSelectorApi.setUp(
32-
pluginBinding.getBinaryMessenger(), fileSelectorApi);
31+
FileSelectorApi.Companion.setUp(pluginBinding.getBinaryMessenger(), fileSelectorApi);
3332
}
3433

3534
@Override
@@ -45,8 +44,7 @@ public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBindin
4544
fileSelectorApi.setActivityPluginBinding(binding);
4645
} else {
4746
fileSelectorApi = new FileSelectorApiImpl(binding);
48-
GeneratedFileSelectorApi.FileSelectorApi.setUp(
49-
pluginBinding.getBinaryMessenger(), fileSelectorApi);
47+
FileSelectorApi.Companion.setUp(pluginBinding.getBinaryMessenger(), fileSelectorApi);
5048
}
5149
}
5250

packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
import java.util.HashSet;
3232
import java.util.List;
3333
import java.util.Set;
34+
import kotlin.Result;
35+
import kotlin.Unit;
36+
import kotlin.jvm.functions.Function1;
37+
import org.jetbrains.annotations.NotNull;
3438

35-
public class FileSelectorApiImpl implements GeneratedFileSelectorApi.FileSelectorApi {
39+
public class FileSelectorApiImpl implements FileSelectorApi {
3640
private static final String TAG = "FileSelectorApiImpl";
3741
// Request code for selecting a file.
3842
private static final int OPEN_FILE = 221;
@@ -91,9 +95,8 @@ public FileSelectorApiImpl(@NonNull ActivityPluginBinding activityPluginBinding)
9195
@Override
9296
public void openFile(
9397
@Nullable String initialDirectory,
94-
@NonNull GeneratedFileSelectorApi.FileTypes allowedTypes,
95-
@NonNull
96-
GeneratedFileSelectorApi.NullableResult<GeneratedFileSelectorApi.FileResponse> result) {
98+
@NonNull FileTypes allowedTypes,
99+
@NonNull Function1<? super Result<FileResponse>, Unit> callback) {
97100
final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT);
98101
intent.addCategory(Intent.CATEGORY_OPENABLE);
99102

@@ -111,32 +114,37 @@ public void onResult(int resultCode, @Nullable Intent data) {
111114
final Uri uri = data.getData();
112115
if (uri == null) {
113116
// No data retrieved from opening file.
114-
result.error(new Exception("Failed to retrieve data from opening file."));
117+
ResultUtilsKt.<FileResponse>completeWithError(
118+
callback, new Exception("Failed to retrieve data from opening file."));
115119
return;
116120
}
117121

118-
final GeneratedFileSelectorApi.FileResponse file = toFileResponse(uri);
122+
final FileResponse file = toFileResponse(uri);
119123
if (file != null) {
120-
result.success(file);
124+
ResultUtilsKt.<FileResponse>completeWithValue(callback, file);
121125
} else {
122-
result.error(new Exception("Failed to read file: " + uri));
126+
ResultUtilsKt.completeWithError(
127+
callback, new Exception("Failed to read file: " + uri));
123128
}
124129
} else {
125-
result.success(null);
130+
ResultUtilsKt.completeWithValue(callback, null);
126131
}
127132
}
128133
});
129134
} catch (Exception exception) {
130-
result.error(exception);
135+
ResultUtilsKt.completeWithError(callback, exception);
131136
}
132137
}
133138

134139
@Override
135140
public void openFiles(
136141
@Nullable String initialDirectory,
137-
@NonNull GeneratedFileSelectorApi.FileTypes allowedTypes,
142+
@NonNull FileTypes allowedTypes,
138143
@NonNull
139-
GeneratedFileSelectorApi.Result<List<GeneratedFileSelectorApi.FileResponse>> result) {
144+
Function1<
145+
? super @NotNull Result<? extends @NotNull List<@NotNull FileResponse>>,
146+
@NotNull Unit>
147+
callback) {
140148
final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT);
141149
intent.addCategory(Intent.CATEGORY_OPENABLE);
142150
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
@@ -155,46 +163,46 @@ public void onResult(int resultCode, @Nullable Intent data) {
155163
// Only one file was returned.
156164
final Uri uri = data.getData();
157165
if (uri != null) {
158-
final GeneratedFileSelectorApi.FileResponse file = toFileResponse(uri);
166+
final FileResponse file = toFileResponse(uri);
159167
if (file != null) {
160-
result.success(Collections.singletonList(file));
168+
ResultUtilsKt.completeWithValue(callback, Collections.singletonList(file));
161169
} else {
162-
result.error(new Exception("Failed to read file: " + uri));
170+
ResultUtilsKt.completeWithError(
171+
callback, new Exception("Failed to read file: " + uri));
163172
}
164173
}
165174

166175
// Multiple files were returned.
167176
final ClipData clipData = data.getClipData();
168177
if (clipData != null) {
169-
final List<GeneratedFileSelectorApi.FileResponse> files =
170-
new ArrayList<>(clipData.getItemCount());
178+
final List<FileResponse> files = new ArrayList<>(clipData.getItemCount());
171179
for (int i = 0; i < clipData.getItemCount(); i++) {
172180
final ClipData.Item clipItem = clipData.getItemAt(i);
173-
final GeneratedFileSelectorApi.FileResponse file =
174-
toFileResponse(clipItem.getUri());
181+
final FileResponse file = toFileResponse(clipItem.getUri());
175182
if (file != null) {
176183
files.add(file);
177184
} else {
178-
result.error(new Exception("Failed to read file: " + uri));
185+
ResultUtilsKt.completeWithError(
186+
callback, new Exception("Failed to read file: " + uri));
179187
return;
180188
}
181189
}
182-
result.success(files);
190+
ResultUtilsKt.completeWithValue(callback, files);
183191
}
184192
} else {
185-
result.success(new ArrayList<>());
193+
ResultUtilsKt.completeWithValue(callback, new ArrayList<>());
186194
}
187195
}
188196
});
189197
} catch (Exception exception) {
190-
result.error(exception);
198+
ResultUtilsKt.completeWithError(callback, exception);
191199
}
192200
}
193201

194202
@Override
195203
public void getDirectoryPath(
196204
@Nullable String initialDirectory,
197-
@NonNull GeneratedFileSelectorApi.NullableResult<String> result) {
205+
@NonNull Function1<? super @NotNull Result<String>, @NotNull Unit> callback) {
198206
final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE);
199207
trySetInitialDirectory(intent, initialDirectory);
200208

@@ -209,7 +217,8 @@ public void onResult(int resultCode, @Nullable Intent data) {
209217
final Uri uri = data.getData();
210218
if (uri == null) {
211219
// No data retrieved from opening directory.
212-
result.error(new Exception("Failed to retrieve data from opening directory."));
220+
ResultUtilsKt.completeWithError(
221+
callback, new Exception("Failed to retrieve data from opening directory."));
213222
return;
214223
}
215224

@@ -219,17 +228,17 @@ public void onResult(int resultCode, @Nullable Intent data) {
219228
try {
220229
final String path =
221230
FileUtils.getPathFromUri(activityPluginBinding.getActivity(), docUri);
222-
result.success(path);
231+
ResultUtilsKt.completeWithValue(callback, path);
223232
} catch (UnsupportedOperationException exception) {
224-
result.error(exception);
233+
ResultUtilsKt.completeWithError(callback, exception);
225234
}
226235
} else {
227-
result.success(null);
236+
ResultUtilsKt.<String>completeWithValue(callback, null);
228237
}
229238
}
230239
});
231240
} catch (Exception exception) {
232-
result.error(exception);
241+
ResultUtilsKt.completeWithError(callback, exception);
233242
}
234243
}
235244

@@ -240,8 +249,7 @@ public void setActivityPluginBinding(@Nullable ActivityPluginBinding activityPlu
240249
// Setting the mimeType with `setType` is required when opening files. This handles setting the
241250
// mimeType based on the `mimeTypes` list and converts extensions to mimeTypes.
242251
// See https://developer.android.com/guide/components/intents-common#OpenFile
243-
private void setMimeTypes(
244-
@NonNull Intent intent, @NonNull GeneratedFileSelectorApi.FileTypes allowedTypes) {
252+
private void setMimeTypes(@NonNull Intent intent, @NonNull FileTypes allowedTypes) {
245253
final Set<String> allMimetypes = new HashSet<>();
246254
allMimetypes.addAll(allowedTypes.getMimeTypes());
247255
allMimetypes.addAll(tryConvertExtensionsToMimetypes(allowedTypes.getExtensions()));
@@ -307,7 +315,7 @@ public boolean onActivityResult(int requestCode, int resultCode, @Nullable Inten
307315
}
308316

309317
@Nullable
310-
GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) {
318+
FileResponse toFileResponse(@NonNull Uri uri) {
311319
if (activityPluginBinding == null) {
312320
Log.d(TAG, "Activity is not available.");
313321
return null;
@@ -351,7 +359,7 @@ GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) {
351359
}
352360

353361
String uriPath;
354-
GeneratedFileSelectorApi.FileSelectorNativeException nativeError = null;
362+
FileSelectorNativeException nativeError = null;
355363

356364
try {
357365
uriPath = FileUtils.getPathFromCopyOfFileFromUri(activityPluginBinding.getActivity(), uri);
@@ -370,20 +378,11 @@ GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) {
370378
} catch (IllegalArgumentException e) {
371379
uriPath = FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH;
372380
nativeError =
373-
new GeneratedFileSelectorApi.FileSelectorNativeException.Builder()
374-
.setMessage(e.getMessage() == null ? "" : e.getMessage())
375-
.setFileSelectorExceptionCode(
376-
GeneratedFileSelectorApi.FileSelectorExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION)
377-
.build();
381+
new FileSelectorNativeException(
382+
FileSelectorExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION,
383+
e.getMessage() == null ? "" : e.getMessage());
378384
}
379385

380-
return new GeneratedFileSelectorApi.FileResponse.Builder()
381-
.setName(name)
382-
.setBytes(bytes)
383-
.setPath(uriPath)
384-
.setMimeType(contentResolver.getType(uri))
385-
.setSize(size.longValue())
386-
.setFileSelectorNativeException(nativeError)
387-
.build();
386+
return new FileResponse(uriPath, contentResolver.getType(uri), name, size, bytes, nativeError);
388387
}
389388
}

0 commit comments

Comments
 (0)