Flutter Web ve Mobil için ekran görüntüsü indirme/kaydetme

Mirkan
Flutter İzmir
Published in
3 min readJun 6, 2022

--

Web ve mobilde çalışmasını umduğum yeni projem için resim kaydetmeye ihtiyacım oldu. Fakat daha önce Flutter Web ile uğraşmamıştım ve bildiğim tek yol FilePicker ile resmi seçip sonrasında kaydetmekti. Peki bilin bakalım Flutter Web’te ne yok? Dosya sistemi.

çok gizli projem: flavius.app

An abstraction to allow working with files across multiple platforms.

Sıkıntı daha File class’ında ortaya çıktı, neyse ki çabucak cross_file paketi ile çözdüm. Fakat açıklamasında yazdığı gibi bu sadece cross platform çalışabilin diye yapılmış basit bir soyutlamaydı. Uint8List ile elimde resim olsa da, yarattığım XFile’da path yine yoktu.

Firebase Storage’a yükleyip web kullanıcıları için link vermeyi düşündüm fakat yine elinizde adam akıllı bir File objesi olmadığından burası da suya düşüyor. Path’i null olan dosya verdin bununla ne yapayım gibi hatalar alıyorsunuz.

Bu arada ekran görüntüsü almak çok kolay, widget’ı screenshot’a sarın, controller verin, controller üzerinden capture methodunu çağırın.

Sonrasında bu resmi indirmek veya galeriye kaydetmek gerek. Şansıma Stackoverflow’da biri nasıl web’te resim indirebileceğimi gösterdi.

Özetle yöntem index.html’e bir js scripti ekleyip, js context üzerinden bu fonksiyonu çağırmak.

<script src="https://cdnjs.cloudflare.com/ajax/libs/amcharts/3.21.15/plugins/export/libs/FileSaver.js/FileSaver.min.js"></script>

Dart kısmı ise:

void saveImg(Uint8List bytes, String fileName) =>
js.context.callMethod("saveAs", [
html.Blob([bytes]),
fileName
]);

Mobil tarafta da image_gallery_saver kullanıp bu tarafı sonlandıracaktım. Ama işler o kadar basit değildi.

Sorunlar üstüne sorunlar

abstract class ImageDownloader {
Future<void> saveImage(Uint8List bytes, String fileName) async {}
}

Hemen herkesin bu işe dalacağı gibi bir ImageDownloader tanımladım. Sonrasında Web ve Mobil implementasyonlarını yazdım.

import 'dart:html' as html; //ignore: avoid_web_libraries_in_flutter
import 'dart:js' as js; // ignore: avoid_web_libraries_in_flutter
import 'dart:typed_data';
class WebImageDownloader implements ImageDownloader {
@override
Future<void> saveImage(Uint8List bytes, String fileName) async {
js.context.callMethod('saveAs', <dynamic>[
html.Blob(<Uint8List>[bytes]),
fileName
]);
}
}

Mobil için:

import 'package:image_gallery_saver/image_gallery_saver.dart';class MobileImageDownloader implements ImageDownloader {
@override
Future<void> saveImage(Uint8List bytes, String fileName) async {
await ImageGallerySaver.saveImage(bytes, quality: 100, name: fileName);
}
}

Hatta işi ileri götürüp, Injectable kullanayım dedim. Birisini mobil, birisini web ortamına kaydederim, onu da kIsWeb sabitine göre verdim mi bu iş biter diye umuyordum.

Fakat Injectable’ın yarattığı dosyada iki sınıfım da var, iki sınıfım da import edilmiş, ve web_image_downloader.dart, dart:js ve html’i import ettiği zaman, proje artık mobilde alttaki hatayı veriyor.

Not found dart:js

dart:io ve dart:html arasında koşullu import yaptıklarını çok görmüştüm.

Fakat sıkıntı, dart:js ve html kullanan dosyam varsa, bu dosya kesinlikle bir yerde importlanmamalı. Özetle compile time değil, run time’da sahne almalı.

Ve bu dosya zaten js’e ve html’e ihtiyaç duyduğundan, gidip de o js’i değil, iOS’taki js’i kullan diyemiyorsunuz. io ve html arasında yapabilme sebepleri, yapanların genelde File’a ihtiyaç duyması ve ikisinde de File olması.

// Bahsettiğim o koşullu import
import 'dart:io' if (dart.library.html) 'dart:html';

Çözüm

ImageDownloader’ı Injectable ile kullanamadığım için repo’ya bir issue açtım. Stackoverflow’a da yazdım. Nazımızın geçtiği mecraları kullanmakta yarar var. Bu süreçte de kodumu şu şekilde değiştirdim:

Önce abstract class’ım olan ImageDownloader’a bir factory methodu ekledim.

factory ImageDownloader() => getImageDownloader();

image_downloader_stub.dart isminde bir dosya oluşturdum.

import 'package:affirmation_app/domain/image_downloader_module/abstract_image_downloader.dart';ImageDownloader getImageDownloader() =>
throw UnsupportedError("ImageDownloader doesn'nt exists for this platform");

getImageDownloader()’ı Web veya Mobil classlarımdan alıyorum. Bunun için de ImageDownloader’da bu iki dosyadan birini koşullu olarak importlamam gerek.

import 'package:affirmation_app/domain/image_downloader_module/image_downloader_stub.dart'
if (dart.library.io) 'package:affirmation_app/domain/image_downloader_module/mobile_image_downloader.dart'
if (dart.library.html) 'package:affirmation_app/domain/image_downloader_module/web_image_downloader.dart';

Burayı kısaca açıklamak gerekirse, stub dosyasının herhangi bir esprisi yok, onun yerine mobil’de(dart:io olan ortamda) mobile_image_downloader.dart, html olan ortamda, yani web ortamında ise web_image_downloader.dart importlanıyor.

stub içinde getImageDownloader olmasının sebebi analyzer bize bağırmasın diye. Zaten kod oraya düşer de ordakini çağırırsa Exception fırlatıyoruz.

Son adımda ise web_image_downloader.dart ve mobile_image_downloader.dart’da, sınıfın dışında dosyanın altına getImageDownloader’ı ekledim.

WebImageDownloader getImageDownloader() => WebImageDownloader();

mobile_image_downloader.dart’ta ise:

MobileImageDownloader getImageDownloader() => MobileImageDownloader();

Buraya kadar okuyanlar için, aynı mantıkta ve çok yardımı dokunan SO postunu buraya bırakıyorum.

--

--