관리 메뉴

Bull

[Dev] LLM을 활용 단어장 앱 개발일지 002: key 기기 보관 본문

일상/개발일지

[Dev] LLM을 활용 단어장 앱 개발일지 002: key 기기 보관

Bull_ 2024. 8. 16. 22:35

심도있게 하고 차근차근 조금씩 코드를 해석하기 위해 분량이 적다.

api key 입력 창

SharedPreferences 패키지로 api key 저장하기

SharedPreferences 패키지는 비동기적으로 모바일 기기와 통신하여 데이터를 파일로 저장해준다. 만약 서비스를 회원 등록없이 간단한 데이터만을 사용하여 기록 저장을 하고 싶다면 SharedPreferences를 사용할 수 있다.

 

난 우선 api key를 통해 내 백엔드 서버를 배포하고 api 통신을 하도록 생각을 하고 있다. 그래서 회원등록이 아닌 개인 api key를 사용하여 서버에 접근할 수 있도록 모바일 기기에 데이터를 저장할 것이다. 그래서 SharedPreferences를 사용하기로 결정했다.

 

순서는 간단하다.

1.  라우팅 설정으로 api key 입력 페이지로 들어갈 때 apiKey prefs 값을 확인하고 이미 설정되어 있다면 /chat 으로, 설정되어 있지 않다면 /(api key 입력 창)으로 들어가게 된다.
2.  api key 입력 페이지에서 버튼을 누르면 apiKey prefs 값을 등록하고 /chat으로 들어간다.

// filename: api_key_input_page.dart
// class ApiKeyInputPage extends StatefulWidget

// ...
Future<void> _registerApiKey() async =>
    SharedPreferences.getInstance().then((prefs) {
      prefs.setString('apiKey', _apiKeycontroller.text);
    });
// ...
basicButton(
  text: '제출',
  onPressed: () async {
    await _registerApiKey();
    context.go('/chat');
  },
)

여기서 간지를 위해 메소드를 한 줄로 구현했다.

prefs는 비동기로 핸드폰 기기와 통신하기 때문에 데이터를 가져올 때는 비동기로 하는 것이 좋지만 안정성을 위해 저장하는 것도 비동기로 해주었다. setString을 통해 문자열 프로퍼티를 저장하였다.

 

한줄 메소드를 람다식을 안 쓰면 다음과 같다.

Future<void> _registerApiKey() async =>
    await SharedPreferences.getInstance().then((prefs) {
      prefs.setString('apiKey', _apiKeycontroller.text);
    });
// ↓↓↓↓
Future<void> _registerApiKey() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('apiKey', _apiKeycontroller.text);
}

라우팅 설정

// filename: router.dart

import 'package:go_router/go_router.dart';
import '../pages/api_key_input_page.dart';
import '../pages/chatting_page.dart';
import '../pages/home_page.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<bool> hasApiKey() async => await SharedPreferences.getInstance()
    .then((prefs) => (prefs.getString('apiKey') != null));

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const ApiKeyInputPage(),
    ),
    GoRoute(
      path: '/chat',
      builder: (context, state) => ChattingPage(),
    ),
    GoRoute(
      path: '/home',
      builder: (context, state) => const HomePage(),
    ),
  ],
  redirect: (context, state) async {
    final currentPath = state.uri.path;

    final hasKey = await hasApiKey();

    switch (currentPath) {
      case '/':
        return (hasKey) ? '/chat' : '/';
      default:
        return null;
    }
  },
);

패키지는 GoRouter를 사용하였다. 이 파일에서 페이지를 관리할 수 있다. 여기서 GoRouterroutes 프로퍼티 만으로도 페이지의 라우팅 설정을 할 수 있지만 나는 라우터 관리창에서 분기문을 구현하고 싶었다. 이 때 redirect 프로퍼티를 사용하면 된다.

 

GoRouter redirect는 페이지가 네비게이션 되기전에 호출 되는데, 가장 최상단의 GoRouterGoRoute 두 객체 모두 redirect 프로퍼티를 가지고 있다. GoRouter는 탑 레벨인 가장 최고 상단에 위치하는 객체이기 때문에 GoRouter의 계층에서 네비게이션될 때 redirect 프로퍼티에 등록된 콜백을 호출한다. switch 문을 보면 알겠지만 특별한 페이지에서 리다이렉션을 원하지 않아도 같은 계층에서 네비게이션될 때 호출되므로 특정 경로에서는 null을 반환시켜주어야 한다.

 

나는 '/'(api key 입력 창)에서 hasKey 메소드를 통해 apiKey prefs 값을 확인한 후 리다이렉션시킨다.

구현은 완성했지만 apiKey의 값을 다시 null로 만들어 api key 입력창으로 가고 싶다면 prefs의 remove 메소드를 통해서 다시 null으로 초기화할 수 있다.

 

redirect 콜백에 다음과 같은 코드를 추가하면 '/'로 가지않고 '/chat'으로 리다이렉션 되는 것을 확인할 수 있다.

print('currentPath: $currentPath');
print('hasKey: $hasKey');

리다이렉션 성공
채팅 창(재탕한 사진)