image_picker라는 라이브러리 추가하기.

android/app/main/manifest.xml 파일 내부에 두줄추가
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

ios/Runner/info.plist 파일 내부에 해당 문장추가
<key>NSCameraUsageDescription</key>
<string>We need your permission to use the camera</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need your permission to access the photo library</string>

direct로 fileupload를 사용해도 되지만 본인은 docker의 niginx 안에 넣기 때문에 post하는 url이 다르기도하고, 재사용성을 위해 util로 빼서 만듬.
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class FileUploadUtil {
static Future<void> uploadFile({
required BuildContext context, // build 할때 상태
required List<File> images, // 두개이상의 이미지를 선택하기 위해 List를 선택
required String uri, // post를 할 uri
}) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(uri)
);
for (var image in images) { // 파일전송할때, body내부 form-data를 이용해서 key-value로 전송을 하도록 구조했었음
var imageFile = await http.MultipartFile.fromPath(
'files', // key값
image.path, // value값은 이미지경로
);
request.files.add(imageFile);
}
var response = await request.send();
if (response.statusCode == 200) { // 파일 업로드가 정상적인지 확인 가능함.
var responseData = await http.Response.fromStream(response);
var fileUrl = responseData.body;
print('File upload Successful');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Upload successful: $fileUrl')),
);
} else {
print('File upload failed');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('File upload failed')),
);
}
}
}
본인은 소셜로그인이후 개인정보추가입력창에서 증명사진 및 서류등록에 사용을 했음.
import 'dart:io';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:gooinpro_parttimer/services/api/loginapi/login_api.dart';
import 'package:gooinpro_parttimer/utils/file_upload_util.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../models/login/login_register_model.dart';
import '../../models/login/login_response_model.dart';
import '../../providers/user_provider.dart';
class RegisterPage extends StatefulWidget {
_RegisterPageState createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
late LoginRegister registerData;
late TextEditingController _birthController;
List<File> _imagesProfile = []; // 증명사진 넣는 file
List<File> _imagesDocument = []; // 추가 서류 넣는 file
final picker = ImagePicker(); // imagepicker 정의
final String baseUrl = dotenv.env['API_UPLOAD_LOCAL_HOST'] ?? 'No API host found';
void initState() {
super.initState();
_birthController = TextEditingController();
}
void didChangeDependencies() {
super.didChangeDependencies();
var userProvider = context.read<UserProvider>();
registerData = LoginRegister(
pemail: userProvider.pemail ?? '',
pname: userProvider.pname ?? '',
pbirth: DateTime.now(),
pgender: true,
proadAddress: '',
pdetailAddress: '',
);
_birthController.text = formatDate(registerData.pbirth);
}
void dispose() {
_birthController.dispose();
super.dispose();
}
String formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date);
}
Future<void> _pickProfileImage() async { // 증명사진 넣는 메소드를 따로 빼서 에뮬레이터의 사진첩에서 선택하는것
final pickedFile = await picker.pickMultiImage();
if (pickedFile != null) {
setState(() {
_imagesProfile = pickedFile.map((pickedFile) => File(pickedFile.path)).toList(); // 선택한 이미지의 위치 파악
});
}
}
Future<void> _pickDocumentImage() async { // 추가서류 넣는 메소드를 따로 빼고
final pickedFile = await picker.pickMultiImage();
if (pickedFile != null) {
setState(() {
_imagesDocument = pickedFile.map((pickedFile) => File(pickedFile.path)).toList();
});
}
}
Future<void> uploadFiles(BuildContext context, List<File> profileImages, List<File> documentImages) async { // 만들어놨던 util의 메소드를 사용
if (_imagesProfile == null){
return;
}
// FileUploadUtil.uploadFile(context: context, images: _imagesProfile!, uri: '$baseUrl/upload/api/partTimer/document'); 안드로이드 용
// FileUploadUtil.uploadFile(context: context, images: _imagesDocument!, uri: '$baseUrl/upload/api/partTimer/profile'); 안드로이드 용
FileUploadUtil.uploadFile(context: context, images: _imagesProfile!, uri: 'http://localhost:8085/upload/api/partTimer/document');
FileUploadUtil.uploadFile(context: context, images: _imagesDocument!, uri: 'http://localhost:8085/upload/api/partTimer/profile');
}
void _onClickSend() async {
login_api api = login_api();
print("이메일: ${registerData.pemail}");
LoginResponse response = await api.registerUser(registerData);
print(response.accessToken);
uploadFiles(context, _imagesProfile, _imagesDocument); // 클릭시 모든 메소드 실행되도록
context.go('/jobposting');
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('회원가입')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Consumer<UserProvider>(
builder: (context, userNotifier, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 이메일
Row(
children: [
Text('이메일: ', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
Expanded(
child: Text(
userNotifier.pemail ?? '',
style: TextStyle(fontSize: 18),
overflow: TextOverflow.ellipsis, // 텍스트 넘칠 때 처리
),
),
],
),
SizedBox(height: 16),
// 이름
Row(
children: [
Text('이름: ', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
Expanded(
child: Text(
userNotifier.pname ?? '',
style: TextStyle(fontSize: 18),
overflow: TextOverflow.ellipsis,
),
),
],
),
SizedBox(height: 16),
// 생년월일
Row(
children: [
Text('생년월일: ', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
Expanded(
child: TextFormField(
controller: _birthController,
readOnly: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
suffixIcon: Icon(Icons.calendar_today),
),
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: registerData.pbirth,
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (pickedDate != null) {
setState(() {
registerData.pbirth = pickedDate;
_birthController.text = formatDate(pickedDate);
});
}
},
),
),
],
),
SizedBox(height: 16),
// 성별 선택
Row(
children: [
Text('성별: ', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
DropdownButton<bool>(
value: registerData.pgender,
onChanged: (value) {
setState(() {
registerData.pgender = value!;
});
},
items: [
DropdownMenuItem(value: true, child: Text('남')),
DropdownMenuItem(value: false, child: Text('여')),
],
),
],
),
SizedBox(height: 16),
// 주소입력
Row(
children: [
Text('주소입력: ', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: '주소를 입력하세요',
),
onChanged: (value) {
setState(() {
registerData.proadAddress = value;
});
},
),
),
],
),
SizedBox(height: 16),
// 증명사진
Row(
children: [
Text('증명사진', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
_imagesProfile.isEmpty
? Text('no image')
: Container(
width: 60, // 고정된 크기로 설정
height: 60,
decoration: BoxDecoration( // 선택한 이미지 미리보기식으로 하나 나옴
image: DecorationImage(
image: FileImage(_imagesProfile[0]),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(8),
),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickProfileImage,
child: Text('사진 선택')),
SizedBox(width: 8),
],
),
SizedBox(height: 16),
// 증명사진
Row(
children: [
Text('보건증', style: TextStyle(fontSize: 18)),
SizedBox(width: 8),
_imagesDocument.isEmpty
? Text('no image')
: Container(
width: 60, // 고정된 크기로 설정
height: 60,
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(_imagesDocument[0]),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(8),
),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickDocumentImage,
child: Text('사진 선택')),
SizedBox(width: 8),
],
),
SizedBox(height: 16),
// 확인 버튼
Center(
child: ElevatedButton(
onPressed: _onClickSend,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 10.0),
child: Text('확인', style: TextStyle(fontSize: 18)),
),
),
),
],
);
},
),
),
);
}
}