본문 바로가기

flutter

튜토리얼 화면 만들기

대부분 앱들을 설치하고 최초 실행 시,
튜토리얼이 진행된다.

개발자가 만드는 서비스가 아무리 쉽고 간단해도
아주 간단한 튜토리얼은 필요하다.

이번엔 이 라이브러리들을 사용해볼 예정이다.

 

 

 

shared_preferences

https://pub.dev/packages/shared_preferences

 

shared_preferences | Flutter package

Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.

pub.dev

 

shared_preferences | Flutter package

Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.

pub.dev

 

shared_preference는 로컬 저장소 라이브러리다.

 

 

앱 내 로컬 스토리지에 키-값 형태로 데이터를 저장하고

필요 시, 앱에 저장된 데이터를 불러올 수 있다.

 

 

튜토리얼을 본 적 없다면 진행 후,

토글값을 바꿔 더 이상 튜토리얼 화면이 안 나오도록 할 수 있다.

 

 

 

 

introduction_screen

https://pub.dev/packages/introduction_screen

 

introduction_screen | Flutter package

Introduction/Onboarding package for flutter app with some customizations possibilities

pub.dev

튜토리얼 화면을 만들기 귀찮을 때 쓰는 라이브러리다.

 

 

 

 

과정

shared_preferences를 쓰기 위한 사전 설정이 필요하다.

 

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './features/my_app.dart';
import './features/tutorial/tutorial_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  final bool hasSeenOnboarding = prefs.getBool('isTutorial') ?? false;
  runApp(ProviderScope(
    child: MaterialApp(
      home: hasSeenOnboarding? MyApp() : TutorialScreen(),
      debugShowCheckedModeBanner: false,
    ),
  ));
}

riverpod 연습 때문에 ProviderScope를 썼는데
그게 아니면 굳이 쓸 필요 없다.

 

 

 

 

다음은 MyApp을 구성한다.

// lib/my_app.dart
import 'package:flutter/material.dart';
import '../utils/route_manager.dart';
import './tutorial/tutorial_screen.dart';

class MyApp extends StatelessWidget{
  const MyApp({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: buildColumn(context),
      ),
    );
  }

  // 위젯을 빌드에 필요한 context를
  Widget buildColumn(BuildContext context){
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        buildElevatedButton(context, TutorialScreen(), "tutorial example"),
      ],
    );
  }

  Widget buildElevatedButton(BuildContext context, Widget target, String buttonName){
    return Container(
      margin: EdgeInsets.all(15),
      child: ElevatedButton(
        onPressed: () {
          RouteManager.moveTo(context, () => target);
        },
        child: Text("${buttonName}")
      ),
    );  
  }
}

 

 

 

 

RouteManager는 Navigator를 편하게 쓰려고 만들었다.

import 'package:flutter/material.dart';

class RouteManager {
  static void moveTo(
    BuildContext context, 
    Widget Function() targetBuilder, {
      bool fullscreenDialog = false,
    }) {
    // future: 조건 확인, 로그, 애니메이션 등 가능
    Navigator.push(
      context, 
      MaterialPageRoute(
        builder: (_) => targetBuilder(),
        fullscreenDialog: fullscreenDialog
      )
    );
  }
}

 

복잡한 설정 할 필요 없이

위젯을 구성하는 핸들러와 화면전환할 위젯만 넘겨주면 된다.

 

나머지는 위 함수에서 알아서 동작한다.

 

 

tutorial_screen

// tutorial_screen.dart
import 'package:flutter/material.dart';
import 'package:introduction_screen/introduction_screen.dart';
import 'package:riverpod_practice/utils/route_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../my_app.dart';
import '../../utils/route_manager.dart';


/// 튜토리얼 화면 예제
/// 필요한 라이브러리 : shared_preferences, introduction_screen
/// shared_preferences로 튜토리얼 완료 여부 값을 앱 내 로컬 저장소에 저장하고
/// 더 이상 튜토리얼이 안 나오도록 처리 가능

class TutorialScreen extends StatelessWidget{
  ScrollController scrollController = ScrollController();
  TutorialScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return IntroductionScreen(
      pages: [
        buildFirstViewModel(),
        buildSecondViewModel(),
        buildThirdViewModel()
      ],
      onDone: () => complete(context),
      onSkip: () => complete(context),
      showSkipButton: true,
      skip: buildText(value: "skip", fontSize: 18.0),
      next: buildText(value: "Next", fontSize: 18.0),
      done: buildText(value: "Done", fontSize: 18.0),
    );
  }
  void complete(BuildContext context) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('isTutorial', true);
    Navigator.pushReplacement(
      context, 
      MaterialPageRoute(
        builder: (context) => MyApp()
      )
    );
  }

  PageViewModel buildFirstViewModel(){
    return PageViewModel(
      useScrollView: false,
      title: "",
      bodyWidget: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          buildText(value: "테스트입니다", fontSize: 25),
          buildText(value: "간단한 튜토리얼 화면입니다.", fontSize: 18),
          Image.asset("assets/test34.png")
        ],
      ),
    );
  }

  PageViewModel buildSecondViewModel(){
    return PageViewModel(
      useScrollView: false,
      title: "",
      bodyWidget: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          buildText(value: "환영합니다"),
          buildText(value: "두번째 화면이에요!"),
          Image.asset("assets/test34.png")
        ],
      ),
    );
  }

  PageViewModel buildThirdViewModel(){
    return PageViewModel(
      useScrollView: false,
      title: "",
      bodyWidget: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          buildText(value: "마지막이에요"),
          buildText(value: "이제 예제는 끝났습니다"),
          Image.asset("assets/test34.png")
        ],
      ),
    );
  }
}

 

 

 

 

 

 

실행결과

 

매우 간단하게 구현할 수 있다.

 

'flutter' 카테고리의 다른 글

Flutter Navigator 사용법  (0) 2024.12.11
Flutter Toast 사용법  (0) 2023.05.24
Flutter TabBar 사용법  (0) 2023.05.23