Skip to content

Commit 95d105d

Browse files
feat(build): support custom <provider> entries in AndroidManifest via pyproject.toml (#6559)
* feat(build): support custom <provider> entries in AndroidManifest via pyproject.toml Add `[tool.flet.android.provider]` for declaring custom `<provider>` entries on the generated `AndroidManifest.xml`. Each table key is the provider's `android:name`; entries become `android:<key>="<value>"` attributes on the generated element. A reserved `meta_data` sub-table emits nested `<meta-data>` children — scalar values render as `android:value="..."`, inline-table values render as `android:<k>="<v>"` so `android:resource="@xml/..."` works. `false` / `{}` skip the entry; `true` and invalid value types fail the build with a clear error. Booleans are normalized to lowercase `"true"/"false"` since Android XML requires them lowercase. The built-in `androidx.core.content.FileProvider` block is unchanged; user-declared providers must not reuse its authorities. Closes #6556. * docs(changelog): reference PR #6559 in the provider entry
1 parent 93a4c27 commit 95d105d

5 files changed

Lines changed: 183 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Improvements
44

55
* Allow `[tool.flet.android.permission]` values to be TOML inline tables in addition to booleans — each `key = "value"` entry adds an `android:<key>="<value>"` attribute to the generated `<uses-permission>` element, unlocking modifiers like `android:maxSdkVersion` and `android:usesPermissionFlags` that real-world Android permissions (e.g. Bluetooth LE) require. The boolean form and the `--android-permissions` CLI flag are unchanged; a non-empty inline table is always emitted, an empty table (`{}`) is treated as `false`, and invalid value types fail the build with a clear error ([#6550](https://github.com/flet-dev/flet/issues/6550), [#6551](https://github.com/flet-dev/flet/pull/6551)) by @FeodorFitsner.
6+
* Add `[tool.flet.android.provider]` for declaring custom `<provider>` entries in the generated `AndroidManifest.xml`. Each table key is the provider's `android:name`; entries become `android:<key>="<value>"` attributes on the generated element. A reserved `meta_data` sub-table emits nested `<meta-data>` children (scalar values render as `android:value="…"`; inline-table values render as `android:<k>="<v>"` so `android:resource="@xml/…"` works). `false` / `{}` skip the entry; `true` and invalid value types fail the build with a clear error. The built-in `androidx.core.content.FileProvider` block is unchanged ([#6556](https://github.com/flet-dev/flet/issues/6556), [#6559](https://github.com/flet-dev/flet/pull/6559)) by @FeodorFitsner.
67
* Upgrade the bundled Pyodide runtime in the `flet build web` template from `0.27.5` to `0.27.7` (includes `micropip` `0.9.0`) ([#6549](https://github.com/flet-dev/flet/pull/6549)) by @FeodorFitsner.
78
* Drop generated `web/canvaskit/` build artifacts (`canvaskit.js`/`.wasm`/`.symbols` and the `chromium/` and `skwasm`/`skwasm_st` variants) from the `flet build web` template — these are produced by the Flutter web build and should not have been committed into the cookiecutter template ([#6549](https://github.com/flet-dev/flet/pull/6549)) by @FeodorFitsner.
89
* Cache the downloaded `flet-build-template.zip` across builds. The build template is bound to an exact Flet version and is immutable, so on every `flet build` / `flet debug` after the first, the CLI now uses a previously-downloaded zip from `$FLET_CACHE_DIR/build-template/v<flet-version>/` (defaulting to `~/.flet/cache/build-template/v<flet-version>/`) instead of re-fetching it via cookiecutter. The CLI also exports `FLET_CACHE_DIR` into the child Gradle process, so `serious_python_android` >= 1.0.1 lands its Python dist tarballs (`python-android-dart-<py>-<abi>.tar.gz`) in the same cache root by default — fixing the multi-minute "Creating app shell" / `downloadDistArchive_*` delay on every Android debug build. Custom `--template` URLs and the local-dev template path are unchanged ([#6555](https://github.com/flet-dev/flet/discussions/6555), [#6558](https://github.com/flet-dev/flet/pull/6558)) by @FeodorFitsner.

sdk/python/examples/apps/flet_build_test/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ path = "src"
9494
[tool.flet.android.meta_data]
9595
"com.google.android.gms.ads.APPLICATION_ID" = "ca-app-pub-3940256099942544~3347511713"
9696

97+
[tool.flet.android.provider]
98+
"rikka.shizuku.ShizukuProvider" = { authorities = "${applicationId}.shizuku", multiprocess = "false", enabled = "true", exported = "true", permission = "android.permission.INTERACT_ACROSS_USERS_FULL" }
99+
97100
[tool.flet.ios.info]
98101
GADApplicationIdentifier = "ca-app-pub-3940256099942544~3347511713"
99102

sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ def setup_template_data(self):
826826
"android.hardware.touchscreen": False,
827827
}
828828
android_meta_data = {}
829+
android_providers = {}
829830

830831
# merge values from "--permissions" arg:
831832
for p in (
@@ -974,6 +975,88 @@ def setup_template_data(self):
974975
else:
975976
self.cleanup(1, f"Invalid Android meta-data option: {p}")
976977

978+
android_providers = merge_dict(
979+
android_providers,
980+
self.get_pyproject("tool.flet.android.provider") or {},
981+
)
982+
983+
def _xml_attr_value(v):
984+
# Android XML expects lowercase booleans.
985+
if isinstance(v, bool):
986+
return "true" if v else "false"
987+
return v
988+
989+
normalized_providers = {}
990+
for key, value in android_providers.items():
991+
if value is False or value == {}:
992+
continue
993+
if value is True:
994+
self.cleanup(
995+
1,
996+
f"Invalid Android provider value for {key}: 'true' is not "
997+
"supported. Use an inline table of attributes, or 'false' "
998+
"to skip.",
999+
)
1000+
if not isinstance(value, dict):
1001+
self.cleanup(
1002+
1,
1003+
f"Invalid Android provider value for {key}: "
1004+
f"{type(value).__name__}. Expected boolean or inline table.",
1005+
)
1006+
normalized = {}
1007+
for ak, av in value.items():
1008+
if ak == "name":
1009+
self.cleanup(
1010+
1,
1011+
f"Invalid Android provider attribute for {key}: "
1012+
"'name' is reserved and is taken from the table key.",
1013+
)
1014+
if ak == "meta_data":
1015+
if not isinstance(av, dict):
1016+
self.cleanup(
1017+
1,
1018+
f"Invalid Android provider meta_data for {key}: "
1019+
f"{type(av).__name__}. Expected inline table.",
1020+
)
1021+
normalized_meta = {}
1022+
for mk, mv in av.items():
1023+
if isinstance(mv, (str, bool, int, float)):
1024+
normalized_meta[mk] = _xml_attr_value(mv)
1025+
continue
1026+
if isinstance(mv, dict):
1027+
normalized_attrs = {}
1028+
for attr_key, attr_value in mv.items():
1029+
if not isinstance(attr_value, (str, bool, int, float)):
1030+
self.cleanup(
1031+
1,
1032+
f"Invalid Android provider meta-data "
1033+
f"attribute value for "
1034+
f"{key}.meta_data.{mk}.{attr_key}: "
1035+
f"{type(attr_value).__name__}. "
1036+
"Expected string, boolean, or number.",
1037+
)
1038+
normalized_attrs[attr_key] = _xml_attr_value(attr_value)
1039+
normalized_meta[mk] = normalized_attrs
1040+
continue
1041+
self.cleanup(
1042+
1,
1043+
f"Invalid Android provider meta-data value for "
1044+
f"{key}.meta_data.{mk}: {type(mv).__name__}. "
1045+
"Expected string, boolean, number, or inline table.",
1046+
)
1047+
normalized["meta_data"] = normalized_meta
1048+
continue
1049+
if not isinstance(av, (str, bool, int, float)):
1050+
self.cleanup(
1051+
1,
1052+
f"Invalid Android provider attribute value for "
1053+
f"{key}.{ak}: {type(av).__name__}. "
1054+
"Expected string, boolean, or number.",
1055+
)
1056+
normalized[ak] = _xml_attr_value(av)
1057+
normalized_providers[key] = normalized
1058+
android_providers = normalized_providers
1059+
9771060
deep_linking_scheme = (
9781061
self.get_pyproject("tool.flet.ios.deep_linking.scheme")
9791062
if self.package_platform == "iOS"
@@ -1126,6 +1209,7 @@ def setup_template_data(self):
11261209
"android_permissions": android_permissions,
11271210
"android_features": android_features,
11281211
"android_meta_data": android_meta_data,
1212+
"android_providers": android_providers,
11291213
"deep_linking": {
11301214
"scheme": deep_linking_scheme,
11311215
"host": deep_linking_host,

sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
android:name="android.support.FILE_PROVIDER_PATHS"
6262
android:resource="@xml/provider_paths" />
6363
</provider>
64+
<!-- flet: provider {% for k, v in cookiecutter.options.android_providers.items() %} {% if v %} -->
65+
<provider android:name="{{ k }}"{% for ak, av in v.items() if ak != 'meta_data' %} android:{{ ak }}="{{ av }}"{% endfor %}{% if not v.get('meta_data') %} />{% else %}>
66+
{% for mk, mv in v.meta_data.items() %}{% if mv is mapping %}<meta-data android:name="{{ mk }}"{% for attr_key, attr_value in mv.items() %} android:{{ attr_key }}="{{ attr_value }}"{% endfor %} />{% else %}<meta-data android:name="{{ mk }}" android:value="{{ mv }}" />{% endif %}
67+
{% endfor %}</provider>{% endif %}
68+
<!-- flet: end of provider {% endif %} {% endfor %} -->
6469
<!-- Don't delete the meta-data below.
6570
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
6671
<meta-data

website/docs/publish/android.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,96 @@ the `pyproject.toml` example above will be translated accordingly into this:
424424
```
425425
</details>
426426
427+
### Providers
428+
429+
A content provider component that supplies app data to other apps or components.
430+
More information [here](https://developer.android.com/guide/topics/manifest/provider-element).
431+
432+
Each provider is declared as a TOML table whose key is the provider's
433+
`android:name` (the fully-qualified class name). The table's entries become
434+
extra `android:<key>="<value>"` attributes on the generated `<provider>`
435+
element. A reserved `meta_data` sub-table emits nested `<meta-data>` children
436+
inside the provider.
437+
438+
See also:
439+
440+
- [`<provider>` element](https://developer.android.com/guide/topics/manifest/provider-element)
441+
442+
#### Resolution order
443+
444+
Its value is determined in the following order of precedence:
445+
446+
1. `[tool.flet.android.provider]`
447+
448+
#### Supported value forms
449+
450+
The value for each provider must be a TOML inline table or sub-table of
451+
attributes. A value of `false` skips the entry entirely; `true` is not
452+
accepted (a `<provider>` with no attributes is meaningless). An empty table
453+
`{}` is also treated as `false`.
454+
455+
Attribute values must be strings, booleans, or numbers — they are written
456+
verbatim into the manifest. The `name` key is reserved (the `android:name`
457+
comes from the table key).
458+
459+
The reserved `meta_data` sub-table emits `<meta-data>` children. Each
460+
sub-entry's key is the `android:name`; the value can be either:
461+
462+
- a scalar (string/bool/number) — rendered as `android:value=""`, or
463+
- an inline table — its entries become `android:<key>="<value>"` attributes
464+
on the `<meta-data>` element (useful for `android:resource="@xml/…"`).
465+
466+
#### Example
467+
468+
<Tabs groupId="pyproject-toml">
469+
<TabItem value="pyproject-toml" label="pyproject.toml">
470+
```toml
471+
[tool.flet.android.provider]
472+
"rikka.shizuku.ShizukuProvider" = { authorities = "${applicationId}.shizuku", multiprocess = "false", enabled = "true", exported = "true", permission = "android.permission.INTERACT_ACROSS_USERS_FULL" }
473+
474+
[tool.flet.android.provider."com.example.MyProvider"]
475+
authorities = "${applicationId}.myprovider"
476+
exported = false
477+
grantUriPermissions = true
478+
479+
[tool.flet.android.provider."com.example.MyProvider".meta_data]
480+
"android.support.FILE_PROVIDER_PATHS" = { resource = "@xml/file_paths" }
481+
"some.other.key" = "some-value"
482+
```
483+
</TabItem>
484+
</Tabs>
485+
<details>
486+
<summary>Template translation</summary>
487+
488+
In the [`AndroidManifest.xml`](index.md#build-template),
489+
the `pyproject.toml` example above will be translated accordingly into this:
490+
491+
```xml
492+
<application>
493+
<provider android:name="rikka.shizuku.ShizukuProvider"
494+
android:authorities="${applicationId}.shizuku"
495+
android:multiprocess="false"
496+
android:enabled="true"
497+
android:exported="true"
498+
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
499+
<provider android:name="com.example.MyProvider"
500+
android:authorities="${applicationId}.myprovider"
501+
android:exported="false"
502+
android:grantUriPermissions="true">
503+
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
504+
<meta-data android:name="some.other.key" android:value="some-value" />
505+
</provider>
506+
</application>
507+
```
508+
</details>
509+
510+
:::note
511+
Flet already declares a built-in `androidx.core.content.FileProvider` with
512+
authorities `${applicationId}.provider`. Declaring another provider that
513+
uses the same authorities will cause the Android manifest merger to fail —
514+
pick a different `android:authorities` value for your custom provider.
515+
:::
516+
427517
### Features
428518
429519
A hardware or software feature that is used by the application.

0 commit comments

Comments
 (0)