Flutter は全く分からないのですが、 Android で「アプリの固有フォルダ」と言えば、そのアプリ以外からはアクセス出来ないと思います。
仰られるように、 Android はバージョン毎に「公開したいファイルをどこに作ればいいのか」がコロコロ変わっている印象 ですので、お使いの Android のバージョンと Flutter でのフォルダ/ディレクトリの得方をしっかりマッチングさせて調べる必要があるように思います。
(Android10 以前では ) メディアファイル以外のファイルは ストレージ アクセス フレームワーク を用いるのが良いかもしれません。
Flutter プラグインを探しましたら以下がありました。※ホンモノかどうか私には分かりませんので、一応ご注意ください。
saf: ^1.0.3+3
以下の環境を構築してプラグインを作って見ました。
main.dart を実行すると(?) StrageAccessFramework のアクティビティで Downloads フォルダが表示されます。フォルダやファイル名を変更しSAVE ボタンを押すとファイルが作られ、 Flutter の画面が出て(戻って) 該当ファイルのコンテンツプロバイダでの URI が表示されます。
…で、これはどうすれば(pub なんとかに公開する以外で)プラグインとして GitHub とかで公開できるんでしょうか(X_X;;;
Flutter
plain
1 >flutter --version
2 Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git
3 Framework • revision c860cba910 (2 weeks ago) • 2022-03-25 00:23:12 -0500
4 Engine • revision 57d3bac3dd
5 Tools • Dart 2.16.2 • DevTools 2.9.2
AndroidStudio
plain
1 Android Studio Bumblebee | 2021.1.1 Patch 2
2 Build #AI-211.7628.21.2111.8193401, built on February 17, 2022
3 Runtime version: 11.0.11+9-b60-7590822 amd64
4 VM: OpenJDK 64-Bit Server VM by Oracle Corporation
5 Windows 10 10.0
6 GC: G1 Young Generation, G1 Old Generation
7 Memory: 1280M
8 Cores: 6
9 Registry: external.system.auto.import.disabled=true
10 Non-Bundled Plugins: Dart (211.7811), org.jetbrains.kotlin (211-1.6.20-release-275-AS7442.40), io.flutter (66.0.1)
AndroidStudio Plugin
Flutter flutter.dev 66.0.1
Dart JetBrains 211.7811
plugin サンプル生成コマンド(うろ覚え...)
plain
1 > flutter create --org com.teratail.q_jmx3uadrrp6bi3 -t plugin --platforms android -a java saf_plugin_sample
サンプルから修正したコード
saf_plugin_sample/example/lib/main.dart
dart
1 import 'package:flutter/material.dart' ;
2 import 'dart:async' ;
3
4 import 'package:flutter/services.dart' ;
5 import 'package:saf_plugin_sample/saf_plugin_sample.dart' ;
6
7 void main ( ) {
8 runApp ( const MyApp ( ) ) ;
9 }
10
11 class MyApp extends StatefulWidget {
12 const MyApp ( { Key ? key } ) : super ( key : key ) ;
13
14 @override
15 State < MyApp > createState ( ) = > _MyAppState ( ) ;
16 }
17
18 class _MyAppState extends State < MyApp > {
19 String _uri = 'Unknown' ;
20
21 @override
22 void initState ( ) {
23 super . initState ( ) ;
24 initPlatformState ( ) ;
25 }
26
27 // Platform messages are asynchronous, so we initialize in an async method.
28 Future < void > initPlatformState ( ) async {
29 String uri ;
30 try {
31 uri = await SafPluginSample . createDocument ( "text/csv" , "test.csv" , "AAA,BBB,CCC\n111,222,333" ) ? ? 'Canceled' ;
32 } on PlatformException {
33 uri = 'Exception' ;
34 }
35
36 if ( ! mounted ) return ;
37
38 setState ( ( ) {
39 _uri = uri ;
40 } ) ;
41 }
42
43 @override
44 Widget build ( BuildContext context ) {
45 return MaterialApp (
46 home : Scaffold (
47 appBar : AppBar (
48 title : const Text ( 'Plugin example app' ) ,
49 ) ,
50 body : Center (
51 child : Text ( 'Running on: $_uri\n' ) ,
52 ) ,
53 ) ,
54 ) ;
55 }
56 }
saf_plugin_sample/lib/saf_plugin_sample.dart
dart
1 import 'dart:async' ;
2
3 import 'package:flutter/services.dart' ;
4
5 class SafPluginSample {
6 static const MethodChannel _channel = MethodChannel ( 'com.teratail.q_jmx3uadrrp6bi3/saf_plugin_sample' ) ;
7
8 static Future < String ? > createDocument ( String type , String title , String contents ) async {
9 final String ? uri = await _channel . invokeMethod (
10 'createDocument' ,
11 < String , dynamic > {
12 'type' : type ,
13 'title' : title ,
14 'contents' : contents
15 } ) ;
16 return uri ;
17 }
18 }
saf_plugin_sample/android/src/main/java/com/teratail/q_jmx3uadrrp6bi3/saf_plugin_sample/SafPluginSamplePlugin.java
java
1 package com . teratail . q_jmx3uadrrp6bi3 . saf_plugin_sample ;
2
3 import android . app . Activity ;
4 import android . content . Intent ;
5 import android . net . Uri ;
6
7 import androidx . annotation . NonNull ;
8
9 import java . io . * ;
10
11 import io . flutter . embedding . engine . plugins . FlutterPlugin ;
12 import io . flutter . embedding . engine . plugins . activity . * ;
13 import io . flutter . plugin . common . * ;
14 import io . flutter . plugin . common . MethodChannel . MethodCallHandler ;
15 import io . flutter . plugin . common . MethodChannel . Result ;
16
17
18 public class SafPluginSamplePlugin implements FlutterPlugin , MethodCallHandler , ActivityAware , PluginRegistry . ActivityResultListener {
19 private static final int REQUEST_CODE = 1 ;
20
21 private MethodChannel channel ;
22 private ActivityPluginBinding binding ;
23 private Result result ;
24 private String contents ;
25
26 @Override
27 public void onAttachedToEngine ( @NonNull FlutterPluginBinding flutterPluginBinding ) {
28 channel = new MethodChannel ( flutterPluginBinding . getBinaryMessenger ( ) , "com.teratail.q_jmx3uadrrp6bi3/saf_plugin_sample" ) ;
29 channel . setMethodCallHandler ( this ) ;
30 }
31
32 @Override
33 public void onDetachedFromEngine ( @NonNull FlutterPluginBinding binding ) {
34 channel . setMethodCallHandler ( null ) ;
35 }
36
37 @Override
38 public void onMethodCall ( @NonNull MethodCall call , @NonNull Result result ) {
39 if ( call . method . equals ( "createDocument" ) ) {
40 if ( binding != null ) {
41 this . result = result ;
42
43 String type = call . argument ( "type" ) ;
44 String title = call . argument ( "title" ) ;
45 contents = call . argument ( "contents" ) ;
46
47 Intent intent = new Intent ( ) ;
48 intent . setAction ( Intent . ACTION_CREATE_DOCUMENT ) ;
49 intent . setType ( type ) ;
50 intent . putExtra ( Intent . EXTRA_TITLE , title ) ;
51 //intent.putExtra(EXTRA_CONTENTS, contents);
52 binding . getActivity ( ) . startActivityForResult ( intent , REQUEST_CODE ) ;
53 }
54 } else {
55 result . notImplemented ( ) ;
56 }
57 }
58
59 @Override
60 public boolean onActivityResult ( int requestCode , int resultCode , Intent data ) {
61 switch ( requestCode ) {
62 case REQUEST_CODE :
63 if ( resultCode != Activity . RESULT_OK ) {
64 result . success ( null ) ; //CANCEL?
65 return true ;
66 }
67 Uri uri = data . getData ( ) ;
68 try ( OutputStream os = binding . getActivity ( ) . getContentResolver ( ) . openOutputStream ( uri ) ;
69 PrintStream ps = new PrintStream ( os ) ; ) {
70 ps . print ( contents ) ;
71 result . success ( uri . toString ( ) ) ;
72 } catch ( IOException e ) {
73 result . error ( "IOException" , e . getLocalizedMessage ( ) , e ) ;
74 }
75 return true ;
76 }
77 return false ;
78 }
79
80 @Override
81 public void onAttachedToActivity ( @NonNull ActivityPluginBinding binding ) {
82 this . binding = binding ;
83 binding . addActivityResultListener ( this ) ;
84 }
85
86 @Override
87 public void onDetachedFromActivity ( ) {
88 binding . removeActivityResultListener ( this ) ;
89 binding = null ;
90 }
91
92 @Override
93 public void onReattachedToActivityForConfigChanges ( @NonNull ActivityPluginBinding binding ) {
94 onAttachedToActivity ( binding ) ;
95 }
96
97 @Override
98 public void onDetachedFromActivityForConfigChanges ( ) {
99 onDetachedFromActivity ( ) ;
100 }
101 }
下記のような回答は推奨されていません。
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。