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()),
);
}
},
),
),
],
),
),
);
}
}