Flutter에서 웹뷰로 이미지 업로드하는 방법은 일반적으로 웹 뷰에 포함된 HTML 파일 내에 Flutter의 이미지 등록 함수를 window 함수로 연결하고, Flutter의 WebView 클래스에서 이를 사용하여 이미지 파일 선택 다이얼로그를 띄우고 선택한 이미지 파일을 반환 받아서 네이티브와 통신하는 것이다.
예를 들어, Flutter에서 WebView 위젯을 구성할 때, onWebViewCreated
콜백을 사용하여 웹뷰 객체를 생성하고,
이 객체를 사용하여 JavaScript와 통신할 수 있다.
이것은 WebView에서 window.flutter_inappwebview.callHandler(handlerName, args)
메소드를 사용하여 호출 할 수 있다.
그리고 네이티브 측에서는 InAppWebView.onWebViewCreated
이벤트 핸들러에서 WebView의 초기화가 완료되었을 때, WebViewController 객체를 얻어서 JavaScript에서 호출되는 함수를 구현하고, 이를 사용하여 네이티브 측으로 데이터를 전달할 수 있다.
<template>
<button @click="gettingImageData">Getting Img data</button>
<img :src="pickedImg"/>
</template>
<script setup>
const pickedImg = ref();
// 핸드폰 이미지 select 기능을 사용하기위해
const gettingImageData = () => {
window.flutterGetImage.postMessage('getImageData');
};
// onMounted로 플러터에서 넘어오는 base64 데이터 -> blob 데이터로 변환
// 추후 flutter 에서 getImageData를 보내면 window함수로 받아오는데 이게 동작한다.
onMounted(() => {
window.getImageData = function (base64Data) {
const binaryString = atob(base64Data);
const uint8Array = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([uint8Array], { type: "image/jpeg" });
pickedImg.value = URL.createObjectURL(blob);
};
});
</script>
그럼 여기서 잠깐 base 64에 대해 알아보자.
Base 64 란 8비트 2진 데이터를 (플랫폼의) 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 가리키는 개념이다.
Base 64 는 데이터를 64진법 으로 나타낸다.
이를 0부터 63까지 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 으로 나타낸다.
atob() 은 인코딩된 Base 64 문자열을 디코드한다.
만약 입력 문자열에 Base 64 에 포함되지 않는 문자 (A-Z,a-z,0–9+/ 이외) 가 입력되면 DOMException 이 발생한다.
예를 들면
window.btoa('Man');
// TWFu
window.btoa('TWFu');
// Man
window.btoa('\u');
// Uncaught SyntaxError: Invalid Unicode escape sequence
window.atob('🙂');
// Uncaught DOMException: Failed to execute 'atob' on 'Window'
var uni = '🙂';
var data = encodeURIComponent(uni);
// %F0%9F%99%82
var encode = window.btoa(data);
// JUYwJTlGJTk5JTgy
var decode = window.atob(encode);
// %F0%9F%99%82
decodeURIComponent(decode);
// 🙂
window.flutterGetImage.postMessage('getImageData');
)을 사용하여 Flutter의 ImagePicker(여기서는 flutterGetImage
)를 호출한다. class HomeScreen extends StatefulWidget {
final String? token;
const HomeScreen({required this.token, Key? key}) : super(key: key);
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
WebViewController? webViewController;
Set<JavascriptChannel>? channel;
final homeUrl = '본인 주소';
int index = 0;
String? _token;
void initState() {
super.initState();
_token = widget.token;
}
void dispose() {
super.dispose();
}
Widget build(BuildContext context) {
void _getImage() async {
final ImagePicker _picker = ImagePicker();
final imageFile = await _picker.pickImage(source: ImageSource.gallery);
if (imageFile != null) {
final Uint8List bytes = await imageFile.readAsBytes();
final String base64 = base64Encode(bytes);
webViewController!.runJavascript('window.getImageData("$base64")');
}
}
return DefaultLayout(
child: FutureBuilder(
future: checkPermission(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data == '위치 권한이 허가되었습니다.') {
return StreamBuilder<Position>(
stream: Geolocator.getPositionStream(),
builder: (context, snapshot) {
if(snapshot.hasData) {}
return WebView(
initialUrl: homeUrl,
onWebViewCreated: (WebViewController webViewController) {
this.webViewController = webViewController;
},
javascriptMode: JavascriptMode.unrestricted,
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: 'flutterGetImage',
onMessageReceived: (JavascriptMessage message) {
_getImage();
},
),
},
);
},
);
}
return const Center(child: Text('연결이 끊겼습니다.'));
}),
);
}
}
Future checkPermission() async {
final isLocationEnabled = await Geolocator.isLocationServiceEnabled();
if (!isLocationEnabled) {
return '위치 서비스를 활성화 해주세요.';
}
LocationPermission checkPermission = await Geolocator.checkPermission();
if (checkPermission == LocationPermission.denied) {
checkPermission = await Geolocator.requestPermission();
if (checkPermission == LocationPermission.denied) {
return '위치 권한을 허가해주세요.';
}
}
if (checkPermission == LocationPermission.deniedForever) {
return '앱의 위치 권한을 세팅에서 허가해주세요.';
}
return '위치 권한이 허가되었습니다.';
}