简体   繁体   English

有没有办法让flutter webview使用android相机进行文件上传? 如何在 webview_flutter flutter 中打开文件选择器?

[英]Is there a way of making a flutter webview use android camera for file upload? How to open file picker in webview_flutter flutter?

I have a flutter web application with a webview that loads my PHP project from a server.我有一个带有webview的 flutter web 应用程序,可以从服务器加载我的 PHP 项目。 In my PHP project, I have a registration form that requires a user to capture photos using the camera then upload it to the MySQL DB.在我的 PHP 项目中,我有一个注册表单,要求用户使用相机拍摄照片,然后将其上传到 MySQL 数据库。 The problem is that when I click the upload file button to use the camera it does nothing.问题是,当我单击上传文件按钮使用相机时,它什么也不做。 But in the browser, the file chooser is working but on my android from the webview it's not doing anything.但是在浏览器中,文件选择器正在工作,但在我的 android 上从webview它没有做任何事情。

I tried this it's not working as well.我试过这个它也不起作用。

<div class="col-sm-4">
Image 1 <span style="color:red">*</span><input type="file" name="img1" accept"image/*" capture="camera"  required>
</div>

Here are my imports too.这里也是我的进口。

import 'package:car_renting_app/Animations/FadeAnimation.dart';
import 'package:car_renting_app/onboarding.dart';
import 'package:car_renting_app/popup.dart';
import 'package:car_renting_app/ui/adminwebview.dart';
import 'package:car_renting_app/widgets/animated_botton_bar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:connectivity/connectivity.dart';

Here is my pubsec.yaml这是我的 pubsec.yaml

cupertino_icons: ^0.1.2
webview_flutter:
simple_animations: ^1.1.3
page_transition: ^1.1.4
carousel_pro: ^0.0.13
url_launcher: ^5.1.1
sliding_up_panel: ^0.3.4
font_awesome_flutter: ^8.5.0
flutter_swiper: ^1.1.6
connectivity:
uni_links:

I think this is an alternative solution using "webview_flutter" : https://github.com/flutter/flutter/issues/27924#issuecomment-647197754我认为这是使用“webview_flutter”的替代解决方案: https : //github.com/flutter/flutter/issues/27924#issuecomment-647197754

Have to edit "FlutterWebView.java" and "AndroidManifest".必须编辑“FlutterWebView.java”和“AndroidManifest”。

@Bruno's link answer did not work for me. @Bruno 的链接答案对我不起作用。 So I found many solutions and used them together and finally it worked for me.所以我找到了很多解决方案并将它们一起使用,最后它对我有用。 webview_flutter: ^2.0.8 (this solution should work with lower version too) webview_flutter: ^2.0.8 (此解决方案也适用于较低版本)

Prerequisite :- Permission handling should be implemented before implementing this solution camera and storage permissions先决条件:- 在实施此解决方案相机和存储权限之前应实施权限处理

  1. Go to External libraries of your flutter转到颤振的外部库

  2. Navigate to webview_flutter plugin project folder导航到 webview_flutter 插件项目文件夹

  3. Navigate to android folder导航到android文件夹

  4. Go to src>main转到 src>main

  5. Open manifest file , it should be empty with just package name in it , then copy paste this打开清单文件,它应该是空的,只有包名,然后复制粘贴这个

     <provider android:name="io.flutter.plugins.webviewflutter.GenericFileProvider" android:authorities="${applicationId}.generic.provider" android:exported="false" android:grantUriPermissions="true" > <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> </application> <queries> <package android:name="com.google.android.apps.photos" /> <package android:name="com.google.android.apps.docs" /> <package android:name="com.google.android.documentsui" /> </queries>
  6. Create res directory if not there under main folder如果主文件夹下没有res目录,则创建res目录

  7. Create values directory then create strings.xml in it: copy and paste this创建值目录,然后在其中创建 strings.xml:复制并粘贴此

<resources>
    <string name="webview_file_chooser_title">Choose a file</string>
    <string name="webview_image_chooser_title">Choose an image</string>
    <string name="webview_video_chooser_title">Choose a video</string>
</resources>
  1. Create xml folder then create provider_paths.xml in it , copy and paste this创建 xml 文件夹,然后在其中创建 provider_paths.xml ,复制并粘贴它

     <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="safetyapp_images" path="images" /> </paths>
  2. Go to main>java>io>flutter>plugins>webviewflutter转到 main>java>io>flutter>plugins>webviewflutter

  3. Create java class with name Constants.java and copy this创建名为 Constants.java 的 java 类并复制它

package io.flutter.plugins.webviewflutter;包 io.flutter.plugins.webviewflutter;

    public class Constants {
        static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
                "action_request_camera_permission_denied";
        static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";
    
        static final String EXTRA_TITLE = "extra_title";
        static final String EXTRA_ACCEPT_TYPES = "extra_types";
        static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
        static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
        static final String EXTRA_FILE_URIS = "extra_file_uris";
        static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";
    
    
        static final String WEBVIEW_STORAGE_DIRECTORY = "images";
    }
  1. Create FileChooserActivity.java and copy this创建 FileChooserActivity.java 并复制它

    package io.flutter.plugins.webviewflutter;包 io.flutter.plugins.webviewflutter;

     import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE; import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.util.Log; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; public class FileChooserActivity extends Activity { private static final int FILE_CHOOSER_REQUEST_CODE = 12322; private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); // List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); showFileChooser( getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false), getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false)); } private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) { Intent getContentIntent = createGetContentIntent(); Intent captureImageIntent = showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null; Intent captureVideoIntent = showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null; if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) { // cannot open anything: cancel file chooser sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED)); finish(); } else { ArrayList<Intent> intentList = new ArrayList<>(); if (getContentIntent != null) { intentList.add(getContentIntent); } if (captureImageIntent != null) { intentList.add(captureImageIntent); } if (captureVideoIntent != null) { intentList.add(captureVideoIntent); } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE)); chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0)); intentList.remove(0); if (intentList.size() > 0) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0])); } startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE); } } private Intent createGetContentIntent() { Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT); if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) { filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES); if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) { // empty array or only 1 empty string? -> accept all types filesIntent.setType("*/*"); } else if (acceptTypes.length == 1) { filesIntent.setType(acceptTypes[0]); } else { // acceptTypes.length > 1 filesIntent.setType("*/*"); filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes); } return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null; } private Intent createCaptureIntent(String type, String fileFormat) { Intent captureIntent = new Intent(type); if (captureIntent.resolveActivity(getPackageManager()) == null) { return null; } // Create the File where the output should go Uri captureOutputUri = getTempUri(fileFormat); potentialCaptureOutputUris.add(captureOutputUri); captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri); return captureIntent; } private File getStorageDirectory() { File imageDirectory = new File(this.getExternalFilesDir(null), WEBVIEW_STORAGE_DIRECTORY); if (!imageDirectory.isDirectory()) { imageDirectory.mkdir(); } return imageDirectory; } private Uri getTempUri(String format) { String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format; File file = new File(getStorageDirectory(), fileName); return FileProvider.getUriForFile( this, getApplicationContext().getPackageName() + ".generic.provider", file); } private String getFileNameFromUri(Uri uri) { Cursor returnCursor = getContentResolver().query(uri, null, null, null, null); assert returnCursor != null; int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); returnCursor.moveToFirst(); String name = returnCursor.getString(nameIndex); returnCursor.close(); return name; } private Uri copyToLocalUri(Uri uri) { File destination = new File(getStorageDirectory(), getFileNameFromUri(uri)); try (InputStream in = getContentResolver().openInputStream(uri); OutputStream out = new FileOutputStream(destination)) { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } return FileProvider.getUriForFile( this, getApplicationContext().getPackageName() + ".generic.provider", destination); } catch (IOException e) { Log.e("WEBVIEW", "Unable to copy selected image", e); e.printStackTrace(); return null; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == FILE_CHOOSER_REQUEST_CODE) { Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED); if (resultCode == Activity.RESULT_OK) { if (data != null && (data.getDataString() != null || data.getClipData() != null)) { if (data.getDataString() != null) { // single result from file browser OR video from camera Uri localUri = copyToLocalUri(data.getData()); if (localUri != null) { fileChooserFinishedIntent.putExtra( EXTRA_FILE_URIS, new String[] {localUri.toString()}); } } else if (data.getClipData() != null) { // multiple results from file browser int uriCount = data.getClipData().getItemCount(); String[] uriStrings = new String[uriCount]; for (int i = 0; i < uriCount; i++) { Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri()); if (localUri != null) { uriStrings[i] = localUri.toString(); } } fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings); } } else { // image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device) for (Uri captureOutputUri : potentialCaptureOutputUris) { try { // just opening an input stream (and closing immediately) to test if the Uri points to a valid file // if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop. getContentResolver().openInputStream(captureOutputUri).close(); fileChooserFinishedIntent.putExtra( EXTRA_FILE_URIS, new String[] {captureOutputUri.toString()}); // leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it break; } catch (IOException ignored) { } } } } sendBroadcast(fileChooserFinishedIntent); finish(); } else { super.onActivityResult(requestCode, resultCode, data); } } }
  2. Create this class FileChooserLauncher.java and copy this创建此类 FileChooserLauncher.java 并复制它

    package io.flutter.plugins.webviewflutter;包 io.flutter.plugins.webviewflutter;

     import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED; import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION; import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.Uri; import android.webkit.ValueCallback; import androidx.core.content.ContextCompat; public class FileChooserLauncher extends BroadcastReceiver { private Context context; private String title; private boolean allowMultipleFiles; private boolean videoAcceptable; private boolean imageAcceptable; private ValueCallback<Uri[]> filePathCallback; private String[] acceptTypes; public FileChooserLauncher( Context context, boolean allowMultipleFiles, ValueCallback<Uri[]> filePathCallback, String[] acceptTypes) { this.context = context; this.allowMultipleFiles = allowMultipleFiles; this.filePathCallback = filePathCallback; this.acceptTypes = acceptTypes; if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) { // acceptTypes empty -> accept anything imageAcceptable = true; videoAcceptable = true; } else { for (String acceptType : acceptTypes) { if (acceptType.startsWith("image/")) { imageAcceptable = true; } else if (acceptType.startsWith("video/")) { videoAcceptable = true; } } } if (imageAcceptable && !videoAcceptable) { title = context.getResources().getString(R.string.webview_image_chooser_title); } else if (videoAcceptable && !imageAcceptable) { title = context.getResources().getString(R.string.webview_video_chooser_title); } else { title = context.getResources().getString(R.string.webview_file_chooser_title); } } private boolean canCameraProduceAcceptableType() { return imageAcceptable || videoAcceptable; } private boolean hasCameraPermission() { return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; } public void start() { if (!canCameraProduceAcceptableType() || hasCameraPermission()) { showFileChooser(); } else { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED); context.registerReceiver(this, intentFilter); Intent intent = new Intent(context, RequestCameraPermissionActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } private void showFileChooser() { IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED); context.registerReceiver(this, intentFilter); Intent intent = new Intent(context, FileChooserActivity.class); intent.putExtra(EXTRA_TITLE, title); intent.putExtra(EXTRA_ACCEPT_TYPES, acceptTypes); intent.putExtra(EXTRA_SHOW_IMAGE_OPTION, imageAcceptable && hasCameraPermission()); intent.putExtra(EXTRA_SHOW_VIDEO_OPTION, videoAcceptable && hasCameraPermission()); intent.putExtra(EXTRA_ALLOW_MULTIPLE_FILES, allowMultipleFiles); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) { context.unregisterReceiver(this); showFileChooser(); } else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) { String[] uriStrings = intent.getStringArrayExtra(EXTRA_FILE_URIS); Uri[] result = null; if (uriStrings != null) { int uriStringCount = uriStrings.length; result = new Uri[uriStringCount]; for (int i = 0; i < uriStringCount; i++) { result[i] = Uri.parse(uriStrings[i]); } } filePathCallback.onReceiveValue(result); context.unregisterReceiver(this); filePathCallback = null; } } }
  3. There should a class called FlutterWebView.java Just copy this overriden method and paste inside it.应该有一个名为FlutterWebView.java的类只需复制这个覆盖的方法并粘贴到其中即可。

     @Override public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // info as of 2021-03-08: // don't use fileChooserParams.getTitle() as it is (always? on Mi 9T Pro Android 10 at least) null // don't use fileChooserParams.isCaptureEnabled() as it is (always? on Mi 9T Pro Android 10 at least) false, even when the file upload allows images or any file final Context context = webView.getContext(); final boolean allowMultipleFiles = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE; final String[] acceptTypes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? fileChooserParams.getAcceptTypes() : new String[0]; new FileChooserLauncher(context, allowMultipleFiles, filePathCallback, acceptTypes) .start(); return true; } }
  4. Create a class RequestCameraPermissionActivity.java and paste this code there创建一个类RequestCameraPermissionActivity.java并将此代码粘贴到那里

import android.Manifest;导入 android.Manifest; import android.app.Activity;导入 android.app.Activity; import android.content.Intent;导入 android.content.Intent; import android.os.Bundle;导入 android.os.Bundle; import androidx.annotation.NonNull;导入 androidx.annotation.NonNull; import androidx.annotation.Nullable;导入 androidx.annotation.Nullable; import androidx.core.app.ActivityCompat;导入 androidx.core.app.ActivityCompat;

    public class RequestCameraPermissionActivity extends Activity {

    private static final int CAMERA_PERMISSION_REQUEST_CODE = 12321;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityCompat.requestPermissions(
                this, new String[] {Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            sendBroadcast(new Intent(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED));
            finish();
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

This is a PR that add onShowFileChooser这是一个添加onShowFileChooser的 PR

https://github.com/flutter/plugins/pull/3225 https://github.com/flutter/plugins/pull/3225

Maybe the next version will support this feature on Android.也许下一个版本会在 Android 上支持这个功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM