I'm working on a FlutterFlow project that utilizes ProImageEditor to allow users to edit images within the app. I've encountered an issue where the edited image is not being returned correctly after the editing process.
The user selects an image to edit, which is then passed to ProImageEditor for editing.
After editing, I expect the edited image bytes to be returned so I can save them on the user's device and display them in the app.
The relevant code for this process is:
The issue is that the bytes of the edited image do not seem to be returned after editing, resulting in an inability to save and display the edited image. The onImageEditingComplete method does not seem to be functioning as expected, or perhaps I am missing a step in the process.
// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'package:extended_image/extended_image.dart';
import 'package:pro_image_editor/pro_image_editor.dart';
import 'package:image_editor/image_editor.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:colorfilter_generator/colorfilter_generator.dart';
import 'package:rounded_background_text/rounded_background_text.dart';
import 'package:screenshot/screenshot.dart';
import 'package:vibration/vibration.dart';
import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart' hide Category;
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:pro_image_editor/widgets/loading_dialog.dart';
import 'package:image/image.dart' as img;
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_core/firebase_core.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
this.width,
this.height,
required this.uploadedFile,
});
final double? width; // Largura opcional para o widget
final double? height; // Altura opcional para o widget
final FFUploadedFile uploadedFile; // O arquivo enviado pelo usuΓ‘rio
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Uint8List? editedImageBytes; // Armazena os bytes da imagem editada
// Define uma variΓ‘vel para armazenar os bytes da imagem a serem exibidos
Uint8List? imageToShow;
// Salva os bytes da imagem editada no dispositivo e retorna o caminho do arquivo
Future<String?> saveImageToDevice(Uint8List imageBytes) async {
try {
// ObtΓ©m o diretΓ³rio de documentos do aplicativo
final directory = await getApplicationDocumentsDirectory();
// Cria um caminho de arquivo ΓΊnico
String fileName =
"edited_image_${DateTime.now().millisecondsSinceEpoch}.png";
File imageFile = File('${directory.path}/$fileName');
// Escreve os bytes da imagem no arquivo
await imageFile.writeAsBytes(imageBytes);
// Retorna o caminho do arquivo onde a imagem foi salva
return imageFile.path;
} catch (e) {
print("Erro ao salvar a imagem no dispositivo: $e");
return null;
}
}
// Local onde vocΓͺ deseja decidir qual imagem mostrar
Future<Uint8List?> getImageToShow() async {
if (FFAppState().imagemEditadaPath != null &&
FFAppState().imagemEditadaPath.isNotEmpty) {
// Tenta carregar a imagem editada do caminho do arquivo de forma assΓncrona
try {
final file = File(FFAppState().imagemEditadaPath);
return await file.readAsBytes();
} catch (e) {
print("Erro ao ler a imagem editada: $e");
return null;
}
} else if (widget.uploadedFile.bytes != null) {
// Retorna os bytes da imagem original enviada
return widget.uploadedFile.bytes;
}
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Botão para iniciar a edição de imagem
OutlinedButton.icon(
onPressed: () async {
if (widget.uploadedFile.bytes != null) {
// Edita a imagem usando ProImageEditor.memory com os bytes do arquivo enviado
var editedBytes = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ProImageEditor.memory(
widget.uploadedFile.bytes!,
onImageEditingComplete: (Uint8List bytes) async {
Navigator.pop(context,
bytes); // Retorna os bytes editados para a tela anterior
},
configs: ProImageEditorConfigs(
// Configuraçáes para o ProImageEditor
// Inclui localização, personalizaçáes de UI e alternÒncias de recursos
// Consulte a documentação do ProImageEditor para detalhes
i18n: const I18n(
various: I18nVarious(
loadingDialogMsg: 'Por favor, aguarde...',
closeEditorWarningTitle:
'Fechar o Editor de Imagens?',
closeEditorWarningMessage:
'Tem certeza de que deseja fechar o Editor de imagens? Suas alteraçáes não serão salvas.',
closeEditorWarningConfirmBtn: 'Ok',
closeEditorWarningCancelBtn: 'Cancelar',
),
paintEditor: I18nPaintingEditor(
bottomNavigationBarText: 'Desenhar',
freestyle: 'Livre',
arrow: 'Seta',
line: 'Linha',
rectangle: 'RetΓ’ngulo',
circle: 'CΓrculo',
dashLine: 'Linha Tracejada',
lineWidth: 'Espessura da linha',
toggleFill: 'Alternar Preenchimento',
undo: 'Desfazer',
redo: 'Refazer',
done: 'Feito',
back: 'Voltar',
smallScreenMoreTooltip: 'Mais',
),
textEditor: I18nTextEditor(
inputHintText: 'Insira o texto',
bottomNavigationBarText: 'Texto',
back: 'Voltar',
done: 'Feito',
textAlign: 'Alinhar texto',
backgroundMode: 'Modo de fundo',
smallScreenMoreTooltip: 'Mais',
),
cropRotateEditor: I18nCropRotateEditor(
bottomNavigationBarText: 'Cortar/Girar',
rotate: 'Girar',
ratio: 'Recortes',
back: 'Voltar',
done: 'Feito',
prepareImageDialogMsg: 'Por favor, aguarde...',
applyChangesDialogMsg: 'Por favor, aguarde...',
smallScreenMoreTooltip: 'Mais',
),
cancel: 'Cancelar',
undo: 'Desfazer',
redo: 'Refazer',
done: 'Feito',
remove: 'Remover',
doneLoadingMsg:
'As alteraçáes estão sendo aplicadas',
),
helperLines: const HelperLines(
showVerticalLine: true,
showHorizontalLine: true,
showRotateLine: true,
hitVibration: true,
),
customWidgets: const ImageEditorCustomWidgets(),
imageEditorTheme: const ImageEditorTheme(
layerHoverCursor: SystemMouseCursors.move,
helperLine: HelperLineTheme(
horizontalColor: Color(0XFF4C68FC),
verticalColor: Color(0XFF4C68FC),
rotateColor: Color(0xFFE91E63),
),
paintingEditor: PaintingEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
lineWidthBottomSheetColor: Color(0xFF252728),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
bottomBarColor: Color(0xFF000000),
bottomBarActiveItemColor: Color(0XFF4C68FC),
bottomBarInactiveItemColor: Color(0xFFEEEEEE),
),
textEditor: TextEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(155, 0, 0, 0),
inputHintColor: Color(0xFFBDBDBD),
inputCursorColor: Color(0XFF4C68FC),
),
cropRotateEditor: CropRotateEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
cropCornerColor: Color(0XFF4C68FC),
),
filterEditor: FilterEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
previewTextColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
),
blurEditor: BlurEditorTheme(
appBarBackgroundColor: Color(0xFF000000),
appBarForegroundColor: Color(0xFFE1E1E1),
background: Color.fromARGB(255, 22, 22, 22),
),
emojiEditor: EmojiEditorTheme(),
stickerEditor: StickerEditorTheme(),
background: Color.fromARGB(255, 22, 22, 22),
loadingDialogTheme: LoadingDialogTheme(
textColor: Color(0xFFE1E1E1),
),
uiOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Color(0x42000000),
statusBarIconBrightness: Brightness.light,
systemNavigationBarIconBrightness:
Brightness.light,
statusBarBrightness: Brightness.dark,
systemNavigationBarColor: Color(0xFF000000),
),
),
icons: const ImageEditorIcons(
paintingEditor: IconsPaintingEditor(
bottomNavBar: Icons.edit_rounded,
lineWeight: Icons.line_weight_rounded,
freeStyle: Icons.edit,
arrow: Icons.arrow_right_alt_outlined,
line: Icons.horizontal_rule,
fill: Icons.format_color_fill,
noFill: Icons.format_color_reset,
rectangle: Icons.crop_free,
circle: Icons.lens_outlined,
dashLine: Icons.power_input,
),
textEditor: IconsTextEditor(
bottomNavBar: Icons.text_fields,
alignLeft: Icons.align_horizontal_left_rounded,
alignCenter:
Icons.align_horizontal_center_rounded,
alignRight: Icons.align_horizontal_right_rounded,
backgroundMode: Icons.layers_rounded,
),
cropRotateEditor: IconsCropRotateEditor(
bottomNavBar: Icons.crop_rotate_rounded,
rotate: Icons.rotate_90_degrees_ccw_outlined,
aspectRatio: Icons.crop,
),
filterEditor: IconsFilterEditor(
bottomNavBar: Icons.filter,
),
emojiEditor: IconsEmojiEditor(
bottomNavBar:
Icons.sentiment_satisfied_alt_rounded,
),
stickerEditor: IconsStickerEditor(
bottomNavBar: Icons.layers_outlined,
),
closeEditor: Icons.clear,
doneIcon: Icons.done,
applyChanges: Icons.done,
backButton: Icons.arrow_back,
undoAction: Icons.undo,
redoAction: Icons.redo,
removeElementZone: Icons.delete_outline_rounded,
),
paintEditorConfigs: const PaintEditorConfigs(
enabled: true,
hasOptionFreeStyle: true,
hasOptionArrow: true,
hasOptionLine: true,
hasOptionRect: true,
hasOptionCircle: true,
hasOptionDashLine: true,
canToggleFill: true,
canChangeLineWidth: true,
initialFill: false,
showColorPicker: true,
freeStyleHighPerformanceScaling: true,
initialStrokeWidth: 10.0,
initialColor: Color(0xffff0000),
initialPaintMode: PaintModeE.freeStyle,
),
textEditorConfigs: const TextEditorConfigs(
enabled: true,
canToggleTextAlign: true,
canToggleBackgroundMode: true,
initFontSize: 24.0,
initialTextAlign: TextAlign.center,
initialBackgroundColorMode:
LayerBackgroundColorModeE.backgroundAndColor,
),
cropRotateEditorConfigs: CropRotateEditorConfigs(
initAspectRatio: 3.0 /
4.0, // Exemplo de como definir um valor inicial
aspectRatios: const [
AspectRatioItem(
text: 'Personalizado', value: null),
AspectRatioItem(
text: 'Original',
value:
0.0), // Supondo que 0.0 representa a proporção original
AspectRatioItem(text: '1x1', value: 1.0 / 1.0),
AspectRatioItem(text: '4x3', value: 4.0 / 3.0),
AspectRatioItem(text: '3x4', value: 3.0 / 4.0),
AspectRatioItem(text: '16x9', value: 16.0 / 9.0),
AspectRatioItem(text: '9x16', value: 9.0 / 16.0),
],
enabled: true,
canRotate: true,
canChangeAspectRatio: true,
),
filterEditorConfigs:
FilterEditorConfigs(enabled: false),
blurEditorConfigs: const BlurEditorConfigs(
enabled: false,
),
emojiEditorConfigs: const EmojiEditorConfigs(
enabled: false,
),
designMode: ImageEditorDesignModeE.material,
heroTag: 'hero',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue.shade800,
brightness: Brightness.dark,
),
),
),
),
),
);
if (editedBytes != null) {
// Salva a imagem editada no dispositivo e obtΓ©m o caminho do arquivo
String? imagePath = await saveImageToDevice(editedBytes);
if (imagePath != null) {
setState(() {
FFAppState().imagemEditadaPath =
imagePath; // Atualiza o estado com o novo caminho da imagem
});
}
}
}
},
icon: const Icon(Icons.edit), // Γcone para o botΓ£o de edição
label: const Text('Editar imagem'), // RΓ³tulo do botΓ£o
// Personalizando a aparΓͺncia do botΓ£o
style: OutlinedButton.styleFrom(
side: BorderSide(
color: Color(0XFF4C68FC),
width: 1), // Cor e espessura da borda
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // Raio da borda
),
foregroundColor: Color(0XFF4C68FC), // Cor do texto e Γcone
),
),
const SizedBox(height: 15), // Espaçamento entre widgets
// Usa um FutureBuilder para esperar pela determinação assΓncrona da imagem a mostrar
Flexible(
child: FutureBuilder<Uint8List?>(
future: getImageToShow(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
// A imagem Γ© exibida dentro de um SizedBox para definir suas dimensΓ΅es mΓ‘ximas.
// O widget de imagem em si irÑ se adaptar para manter sua proporção dentro deste espaço.
return SizedBox(
width: double.infinity, // Usa toda a largura disponΓvel
height: 500, // Altura mΓ‘xima
child: Image.memory(
snapshot.data!,
fit: BoxFit
.contain, // Mantém a proporção da imagem sem cortÑ-la
),
);
} else {
// Exibe um placeholder ou um widget de carregamento enquanto a imagem estΓ‘ sendo preparada
return SizedBox(
width: double.infinity,
height: 500,
child: Center(child: CircularProgressIndicator()),
);
}
},
),
),
],
),
),
);
}
}