Основная задача - осуществить передачу (загрузку) файла картинки выбранной пользователем на сервер, после получения ответа от сервера о загруженном файле отобразить картинку на странице. Для тестирования используется браузер Google Chrome.
Реализация задачи может быть осуществлена с применением различных пакетов и библиотек, облегчающих (вероятно) работу с передачей данных на сервер. Чаще всего для подобных задач используется пакет DIO из библиотеки "dart: io". Но здесь мы попытаемся осуществить передачу данных наиболее простым способом, используя class HttpRequest библиотеки "dart: html". Также не будем обращать сильно внимание на безопасность, так как основная цель - показать простой способ загрузки файла.
Вид единственной страницы, на которой будем осуществлять все необходимые действия:
После нажатия на кнопку "Выбрать" открывается диалоговое окно выбора файла (показан набор файлов на отдельном пользовательском устройстве):
После выбора файла и нажатия на кнопку "Открыть" произойдет отправка выбранного файла на сервер, где отправленные данные будут обработаны РНР скриптом:
Если РНР скриптом будут получены коррректные данные, то файл будет сохранен в папку "userfiles", под тем же именем, которое ему было присвоено на устройстве пользователя. Затем сервер отправит назад в формате JSON массив параметров файла из $_FILES, название файла из $_POST, время и title.
Полученные данные от сервера можно будет посмотреть в терминале, они будут иметь подобный вид:
После получения данных от сервера обновится состояние виджета страницы и мы покажем на ней картинку загруженную на сервер:
Есть один нюанс о котором стоит упомянуть. При тестировании на локальном компьютере из-за политики CORS могут быть недоступны файлы на удаленном сервере. И это может привести не к показу загруженной на сервер картинки, а к выводу вместо нее подобного сообщения на странице:
Решение этой проблемы было найдено на ресурсе stackoverflow. Там было предложено запускать flutter для тестирования следующей командой:
Стоит отметить, что применять эту команду надо будет при каждом запуске flutter, когда будет необходимо получать данные по ссылке на удаленный сервер. Соответственно и на сервере должны быть сняты ограничания доступа.
Теперь перейдем непосредственно к коду. Для удобства, крупные файлы буду рассматривать по частям.
main.dart:
В основном здесь должно быть все понятно для программистов, которые уже в теме работы с фреймворком Flutter. Небольшие пояснения к некоторым строкам были даны здесь.
upload_screen.dart часть 1:
Рассмотрим наиболее значимые строки.
строка №2 (file: upload_screen.dart)
import 'dart:html' as html_d;
Подключаем библиотеку в которой имеется класс HttpRequest, с помощью которого будем осуществлять отправку данных на сервер. Обращаться к библиотеке будем по алиасу (псевдониму) html_d, что будет проще и не будем путаться в аббревиатурах. На скрине строка подчеркнута - это предупреждение от VS Code о том, что данная библиотека используется только в WEB.
строка №3 (file: upload_screen.dart)
import 'dart:convert';
Подключаем библиотеку для обработки данных в формате JSON.
строка №5 (file: upload_screen.dart)
class UploadScreen extends StatelessWidget {
Объявляем класс виджета, который будет показан на странице.
строка №14 (file: upload_screen.dart)
child: UploadFormField(),
Объявляем дочерний виджет, который будет размещен по центру виджета страницы.
строка №19 (file: upload_screen.dart)
class UploadFormField extends StatefulWidget {
Объявляем класс виджета с состоянием, который будет изменяться при отправке данных на сервер и получении от него ответной информации.
строка №22 (file: upload_screen.dart)
State <UploadFormField> createState() => _UploadFormFieldState();
Объявляем виджет состояния.
upload_screen.dart часть 2:
строка №25 (file: upload_screen.dart)
class _UploadFormFieldState extends State<UploadFormField> {
Объявляем класс виджета состояния.
строка №26 (file: upload_screen.dart)
final String _pathScript = "http://test.a77r.ru/test_upload_file.php";
Определяем защищенную переменную _pathScript, в которой записан адрес скрипта на сервере.
строка №27 (file: upload_screen.dart)
String _imagePath = '';
Определяем защищенную переменную _imagePath, в которой будет записана ссылка на загруженную на сервер картинку. Первоначальное значение переменной - пустая строка.
строка №29 (file: upload_screen.dart)
void updateView(String imgPath) {
Объявляем функцию, которая будет проверять соответствие полученного адреса картинки с сервера (переменная imgPath) со значением текущим в защищенной переменной _imagePath. Если они равны, а это возможно, когда в обеих переменных находятся пустые строки, то прекращаем исполнение функции (см. строка 30). В противном случае, запускаем перерисовку виджета и изменяем значение защищенной переменной _imagePath, присваиваем ей полученную ссылку на картинку (см. строки 31-33).
строка №36 (file: upload_screen.dart)
void loadFile() async {
Определяем асинхронную функцию загрузки файла (см. строки 36-60). В функции будет сформирован объект FormData (см. строки 43-45), который будет отправлен на сервер (см. строка 47). Результатом будет передача адреса картинки загруженной на сервер или пустой строки, в зависимости от полученного ответа, в вызываемую функцию updateView (см. строка 59).
строка №38 (file: upload_screen.dart)
final htmlFile = await pickHtmlFile();
Определяем переменную htmlFile и присваиваем ей значение полученное от вызванной функции pickHtmlFile (см. строки 62-69). Функция асинхронная, поэтому дожидаемся получения ответа от нее. После получения ответа в переменной будет храниться объект выбранного пользователем файла.
строка №41 (file: upload_screen.dart)
if (htmlFile.size > 1000000) return;
Проверка размера выбранного файла, если размер превышает 1 Мб, то прекращаем выполнение. Это сделано для примера, как вариант улучшения безопасности. Могут быть и другие решения для обработки входных данных файла.
строка №43 (file: upload_screen.dart)
final formData = html_d.FormData();
Создаем объект формы и присваиваем переменной formData.
строка №44 (file: upload_screen.dart)
formData.appendBlob('userfile', htmlFile, htmlFile.name);
Добавляем в объект формы данные загруженного файла. В скрипте РНР будем получать в массиве данных $_FILES.
строка №45 (file: upload_screen.dart)
formData.append('namefile', htmlFile.name);
Добавляем в объект формы данные имени файла. В скрипте РНР будем получать в массиве данных $_POST. Это сделано для примера, т.к. имя можно получить из данных предыдущей строки. Основная цель - показать возможность передачи дополнительных данных.
строка №47 (file: upload_screen.dart)
html_d.HttpRequest.request(_pathScript, method: 'POST', sendData: formData)
Отправка объекта данных на сервер методом POST.
строка №48 (file: upload_screen.dart)
.then((html_d.HttpRequest rspns) {
Полученный от сервера ответ присваиваем переменной rspns.
строка №49 (file: upload_screen.dart)
print(rspns.responseText);
Выводим полученный от сервера ответ в консоль терминала.
строка №52 (file: upload_screen.dart)
var result = json.decode(rspns.responseText.toString());
Декодируем полученный ответ от сервера из формата JSON.
строка №53 (file: upload_screen.dart)
imagePath = 'http://test.a77r.ru/userfiles/' + result['file_name_post'];
Формируем адрес ссылки на картинку на основании полученных от сервера данных.
строка №55 (file: upload_screen.dart)
}).catchError((error) {
Блок обработки ошибок. Для примера выводятся в консоль, тип ошибки и в какой фазе появилась ошибка (см. строки 56-57). Возможны варианты обработки ошибок и реагирования на них. Если ошибка произошла, то переменной imagePath присваивается значение пустой строки, что прекратит выполнение в функции updateView (см. строка 30).
строка №59 (file: upload_screen.dart)
}).whenComplete(() => updateView(imagePath));
Запускаем функцию updateView и передаем в нее значение переменной imagePath - путь к картинке на сервере.
строка №62 (file: upload_screen.dart)
Future<html_d.File?> pickHtmlFile() async {
Опеределяем асинхронную функцию pickHtmlFile, которая обрабатывает диалог по выбору файла на устройстве пользователя.
строка №63 (file: upload_screen.dart)
final uploadElement = html_d.FileUploadInputElement()
Создаем объект элемента загрузки файла и присваиваем его переменной uploadElement.
строка №64 (file: upload_screen.dart)
..multiple = false
Обозначаем, что объект загрузки файла не будет поддерживать множественный выбор. Это значение задается по умолчанию и здесь приводится для примера.
строка №65 (file: upload_screen.dart)
..accept = 'image/*'
Задаем перечень файлов, которые будут доступны для выбора в диалоговом окне. В нашем случае это будут картинки разных форматов.
строка №66 (file: upload_screen.dart)
..click();
Запуск открытия диалогового окна для выбора файла пользователем.
строка №67 (file: upload_screen.dart)
await uploadElement.onChange.first;
Ожидаем изменений в объекте загрузки файла.
строка №68 (file: upload_screen.dart)
return uploadElement.files?.first;
Возвращаем значение из объекта загрузки файла.
Далее (см строки 72-89) достаточно стандартный код для построения виджета. Стоит обратить внимание на отображение картинки, она будет выводиться только когда длина строки адреса будет больше нуля (см. строку 78)
строка №79 (file: upload_screen.dart)
? Image.network(_imagePath)
Если длина строки адреса будет больше нуля, то будет отображаться картинка, адрес которой содержиться в защищенной переменной _imagePath.
Удачи в разработке!