Flutter와 Firebase로 Android iOS 둘 다 만들기 19 예외 처리하기

3 분 소요

간단하게 예외처리에 대해 알아보고 플러터의 스낵바로 표현 해봅니다.

개요

예외처리는 대부분의 상황에서 if else로 처리가 가능하지만, 잘 모르는 상황이 있을 수 있습니다. 특히 Future로 미래에 응답을 반환할 때 그런 경우가 많습니다.

if else로 처리가 가능한 상황

int a = 3, b = 0;
div (a, b) {
  if (b == 0) return 0;
  return a / b;
}

일반적인 예외처리입니다.

여러 플랫폼을 다룰 때 항상 먼저 해보는 에러처리는 습관 적으로 0으로 나누기를 해봅니다. dart는 0으로 나눌 경우 에러를 내지 않고 Infinity 라는 값을 줍니다.

if else로 처리가 불가능한 상황

지난번 코드의 이메일로 로그인하기를 해보면 알 수 있습니다.

SignInButton(
  Buttons.Email,
  onPressed: () async {
    await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: emailController.text, 
      password: passwordController.text,
    );
  }

이메일 로그인 메쏘드는 뻔한 곳에 뻔하게 잘 정의되어 있기 때문에 따로 설명은 필요 없을 것 같습니다.

디버그 중에 눌러보면 이런 에러를 만날 수 있습니다.

PlatformException(ERROR_OPERATION_NOT_ALLOWED, The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section., null)

파이어베이스 프로젝트에 이메일로그인 설정이 안되어 있다는 것입니다.

이것은 파이어베이스 콘솔 페이지에서 인증에서 쉽게 바꿀 수 있습니다.

현재는 예외처리에 대한 내용이므로 회원가입때 다루겠습니다.

에러만 나오는 것이 아니라 디버그 중이라 해당 위치에 브레이크가 걸리면서 동작이 멈추게 됩니다.

결국 에러 때문에 의도된 동작을 못할 때가 문제인 것입니다.

try catch

async 함수 내부라면 간단하게 try catch로 잡아 낼 수 있습니다.

onPressed: () async {
  try {
    await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: emailController.text, 
      password: passwordController.text,
    );
  } catch (e) {
    print(e);
    print(e.runtimeType);
    print(e.code);    
    print(e.message);
  }
}

catch의 e는 dynamic 입니다.

어떤 형태로든 에러를 만들 수 있는 것입니다.

FirebaseAuth가 만든 e라는 놈은 PlatformException 라는 형태이지만 다른 형태로도 만들 수 있는 것입니다.

참고: https://api.flutter.dev/flutter/services/PlatformException-class.html

PlatformException은 플러터의 서비스 (flutter/services.dart) 에 추가 되어 있는 것입니다.

  • PlatformException
    • code(String)
    • message(String)
    • detail(dynamic)

그러니 e.runtimeType이 PlatformException이 아니라면 e.message는 없을 수도 있으니 주의해야합니다.

화면에 표시해야 할 것은 e.message 가 되는 것입니다.

혹은 e.code로 다양한 출력이나 국제화를 고려한 코딩을 할 수도 있습니다.

throw

일부러 에러를 내고 싶을 때도 있습니다.

try {
  if (passwordController.text.length < 4) throw('short!!!');
  await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: emailController.text, 
    password: passwordController.text,
  );
} catch (e) {
  print(e);
  print(e.runtimeType);
  print(e.message);
  print(e.code);
}

보통 catch에서 공통 UI로 처리하고 싶을 때 이렇게 하는데요..

위에서 언급한대로 다양한 에러를 만들 수 있는데 위의 예시는 String을 넘긴 것입니다.

그래서 e.message나 e.code는 당연히 없는 것이죠..

FirebaseAuth 처럼 에러를 만드려면 똑같은 인스턴스를 만들면 됩니다.

throw(PlatformException(code: 'xx', message: 'short!!'));

finally

에러가 나든 안나든 최종적으로 해야할 일이 필요할 때가 있습니다.

로딩을 보여준다고 하면

try {
  setState(() => _loading = true);
  await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: emailController.text, 
    password: passwordController.text,
  );
  setState(() => _loading = false);
} catch (e) {
  print(e.message);
} 

에러가 나든 안나든 로딩을 꺼줘야하는데 이렇게 하면 에러가 났을 때 로딩이 꺼지지 않습니다.

그럴 때 finally를 사용하면 됩니다.

try {
  setState(() => _loading = true);
  await FirebaseAuth.instance.signInWithEmailAndPassword(
    email: emailController.text, 
    password: passwordController.text,
  );
} catch (e) {
  print(e.message);
} finally {
  setState(() => _loading = false);
}

이러면 에러가 나든 안나든 로딩은 꺼지게 됩니다.

예외처리는 자스와 비슷한 느낌을 많이 받습니다. 제가 자스를 많이 다루는 쪽이라 설명이나 코딩이 비슷해지는 것일 수도 있습니다. 제가 꼭 정답은 아니니 참고만 하시고 각자의 스타일대로 하시면 됩니다.

스낵바로 에러 표시하기

화면 하단에 일정시간 동안 간단한 정보를 표시하는 형태를 스낵바 혹은 토스트라 합니다.

참고: https://flutter-ko.dev/docs/cookbook/design/snackbars

플러터에서 기본으로 제공하는 스낵바 예제로 보면 코드는 단순합니다.

final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));
Scaffold.of(context).showSnackBar(snackBar);

아쉽게도 이렇게 테스트하면 동작하지 않습니다.

Scaffold.of() called with a context that does not contain a Scaffold

Scaffold가 포함된 클래스 내부에서는 동작하지 않는 것입니다.

고작 스낵바 만들려고 위의 예제 처럼 클래스를 하나 만들어줘야하는 것입니다.

그럴 때 키로 호출하는 방법이 있습니다.

지난번 폼 검사할때 처럼 스캐폴드도 스테이트를 키를 만들어 줍니다.

final _scaffoldKey = GlobalKey<ScaffoldState>();

@override
Widget build(BuildContext context) {
  return Scaffold(
    key: _scaffoldKey,
    appBar: AppBar(title: Text('Sign in'), ),
    // ..
  );
}

이제 scaffold의 키로 클래스 내부에서 스낵바를 호출 할 수 있습니다.

final snackBar = SnackBar(content: Text('Yay! A SnackBar!'));
_scaffoldKey.currentState.showSnackBar(snackBar);

메써드로 만들기

다양한 곳에서 사용하기 위해…

./methods/toast.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

toastError(GlobalKey<ScaffoldState> key, dynamic e) {
  String message = 'unknown error';
  if (e is PlatformException) message = e.message;
  final snackBar = SnackBar(content: Text(message));
  key.currentState.showSnackBar(snackBar);  
}

이렇게 정리해봅니다.

역시 자스(vue.js 같은..)에서 하던 느낌으로 대부분의 코딩을 정리하게 됩니다.

fluttertoast

기본 스낵바가 귀찮거나, 좀 더 색다른 표시를 원하시면 외부 모듈을 사용하는 것도 방법입니다.

스캐폴드 위치 상관 없이 아무데서나 잘 튀어나오는 것을 확인했습니다.
주변요소와 잘 어울리게 하면 좋지만… 저는 디자인 감이 떨어져서 그냥 기본 스낵바로 돌아왔습니다..

참고: https://pub.dev/packages/fluttertoast

마치며

로그인,회원가입등에서 사용하기 위해 최소한의 장치만 갖춘 것입니다.

위에서 언급한대로 제 코드는 주류코드가 아닙니다.

관리의 편의를 위해 웹쪽과 비슷한 느낌으로 코딩하는 것이니 개념만 이해하고 직접 구현해보시는 것이 나을 수도 있습니다.

소스

https://github.com/fkkmemi/ff2

영상

댓글남기기