Flutter – 랜덤 번호 생성(2) 스크린 전환

아이콘을 클릭했을때 다른 페이지로 이동하는 방법

 IconButton(
          onPressed: () {
            // list add
            // [HomeScreen(),SettingsScreen()] 마지막에 푸시 push
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return SettingsScreen(); // SettingsScreen페이지로 이동
                },
              ),
            );
          },

새로운 스크린 작업 (setting 페이지)

import 'package:flutter/material.dart';
import 'package:lottery_number_randomly/constant/color.dart';

class SettingsScreen extends StatefulWidget {
  const SettingsScreen({super.key});

  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  double maxNumber = 10000;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: PRIMARY_COLOR,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Expanded(
                child: Row(
                  children: maxNumber
                      .toInt()
                      .toString()
                      .split('')
                      .map(
                        (e) => Image.asset(
                          'asset/img/$e.png',
                          width: 50.0,
                          height: 70.0,
                        ),
                      )
                      .toList(),
                ),
              ),
              Slider(
                value: maxNumber,
                min: 10000,
                max: 1000000,
                onChanged: (double val) {
                  setState(() {
                    maxNumber =
                        val; // slide 값이 변경될때마다 build() 를 호출하여 Slider에게 value 값으로써 넣어줘야 바가 움직인다.
                  });
                },
              ),
              ElevatedButton(
                onPressed: () {},
                style: ElevatedButton.styleFrom(
                  backgroundColor: RED_COLOR,
                ),
                child: Text('저장'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

스크린별 데이터 주고 받기

ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop(maxNumber.toInt()); // pop : 뒤로가기, pop에 인자로 데이터를 넘겨주면 push를 호출한 페이지에서 받을 수 있음
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: RED_COLOR,
                ),
                child: Text('저장'),
              ),
        IconButton(
          onPressed: () async { // 다른 스크린으로 부터 데이터를 돌려받을때는 async (비동기)로 처리해야함
            final result = await Navigator.of(context).push<int>( // push를 실행한 곳에서 데이터를 돌려받을 수 있다.
              MaterialPageRoute(
                builder: (BuildContext context) {
                  return SettingsScreen();
                },
              ),
            );
          },

_Header에서 값을 받아왔으므로 State쪽으로 받아온 데이터를 올려서 한번에 관리되어야한다. 그럼 _Header 클래스에 VoidCallback onPressed 정의하고 State 클래스에서 _Header 클래스 호출시 매개변수로 넘기도록 한다.

코드 정리 (State에서 상태 관리)

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:lottery_number_randomly/constant/color.dart';
import 'package:lottery_number_randomly/screen/settings_screen.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int maxNumber = 1000;
  List<int> randomNumbers = [
    123,
    456,
    789,
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: PRIMARY_COLOR,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 16.0,
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _Header(onPressed: onSettingsPop),
              _Body(randomNumbers: randomNumbers),
              _Footer(onPressed: onRandomNumberGenerate),
            ],
          ),
        ),
      ),
    );
  }

  void onSettingsPop() async {
    // 다른 스크린으로 부터 데이터를 돌려받을때는 async (비동기)로 처리해야함

    /*
    * 아무것도 안돌려받을 수도 있음. 버튼을 누르지 않고 그냥 뒤로가기를 눌렀을때
    * 데이터를 리턴 받는 페이지는 항상 들어오는 데이터가 null 될 수 있다는 가정하에 코딩을 해야함
    * */
    final int? result = await Navigator.of(context).push<int>(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return SettingsScreen();
        },
      ),
    );
    if(result != null){
      setState(() {
        maxNumber = result;
      });
    }
  }

  void onRandomNumberGenerate() {
    final rand = Random();
    final Set<int> newNumbers = {}; // 중복 숫자 제거하기 위해 Set

    while (newNumbers.length != 3) {
      final number = rand.nextInt(maxNumber); // max 1000 이하 숫자 랜덤생성
      newNumbers.add(number);
    }

    setState(() {
      randomNumbers = newNumbers.toList();
    });
  }
}

class _Header extends StatelessWidget {
  final VoidCallback onPressed;

  const _Header({required this.onPressed, super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          '랜덤 숫자 생성기',
          style: TextStyle(
            color: Colors.white,
            fontSize: 30.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        IconButton(
          onPressed: onPressed,
          icon: Icon(
            Icons.settings,
            color: RED_COLOR,
          ),
        ),
      ],
    );
  }
}

class _Body extends StatelessWidget {
  final List<int> randomNumbers;

  const _Body({required this.randomNumbers, super.key});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: randomNumbers
            .asMap()
            .entries
            .map(
              (x) => Padding(
                padding: EdgeInsets.only(
                    bottom:
                        x.key == 2 ? 0 : 16.0), // 마지막 7,8,9 아래는 padding을 넣지 않음
                child: Row(
                  children: x.value
                      .toString()
                      .split('')
                      .map(
                        (y) => Image.asset(
                          'asset/img/$y.png',
                          height: 70.0,
                          width: 50.0,
                        ),
                      )
                      .toList(),
                ),
              ),
            )
            .toList(),
      ),
    );
  }
}

class _Footer extends StatelessWidget {
  final VoidCallback onPressed; // onPress 함수를 외부에서 받아오는 방법 외워둘것

  const _Footer({required this.onPressed, super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity, // width 무한대
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: RED_COLOR,
        ),
        onPressed: onPressed,
        child: Text('생성하기'),
      ),
    );
  }
}

setting screen 데이터 유지(home으로 갔다가 다시 setting_screen으로 넘어왔을때)

pop()을 통해 데이터를 넘겨주고 나면, 기존 setting screen 의 state는 모두 dispose() 되고 다시 setting screen 으로 넘어갈때는 새로운 state 를 생성해서 넘어간다. 그래서 항상 1000으로 고정되어있는 것을 볼 수 있다.

이 값을 유지하기 위해서는 받은 maxNumbe 값을 settings screen state 가 생성될때 다시 넘겨주면 됨.

//settings_screen.dart
final int? result = await Navigator.of(context).push<int>(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return SettingsScreen(
            maxNumber: maxNumber,
          );
        },
      ),
    );
//settings_screen.dart

  final int maxNumber;
  const SettingsScreen({required this.maxNumber, super.key});

  void initState() {
    //
    // TODO: implement initState
    super.initState();
    maxNumber = widget.maxNumber
        .toDouble(); // build() 가 되기 전, home 스크린으로 부터 받은 maxNumber를 변경해준다.
  }

최종

// lib/screen/home_screen.dart
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:lottery_number_randomly/component/number_row.dart';
import 'package:lottery_number_randomly/constant/color.dart';
import 'package:lottery_number_randomly/screen/settings_screen.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int maxNumber = 1000;
  List<int> randomNumbers = [
    123,
    456,
    789,
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: PRIMARY_COLOR,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 16.0,
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _Header(onPressed: onSettingsPop),
              _Body(randomNumbers: randomNumbers),
              _Footer(onPressed: onRandomNumberGenerate),
            ],
          ),
        ),
      ),
    );
  }

  void onSettingsPop() async {
    // 다른 스크린으로 부터 데이터를 돌려받을때는 async (비동기)로 처리해야함
    /*
    * 아무것도 안돌려받을 수도 있음. 버튼을 누르지 않고 그냥 뒤로가기를 눌렀을때
    * 데이터를 리턴 받는 페이지는 항상 들어오는 데이터가 null 될 수 있다는 가정하에 코딩을 해야함
    * */
    final int? result = await Navigator.of(context).push<int>(
      MaterialPageRoute(
        builder: (BuildContext context) {
          return SettingsScreen(
            maxNumber: maxNumber,
          );
        },
      ),
    );
    if (result != null) {
      setState(() {
        maxNumber = result;
      });
    }
  }

  void onRandomNumberGenerate() {
    final rand = Random();
    final Set<int> newNumbers = {}; // 중복 숫자 제거하기 위해 Set

    while (newNumbers.length != 3) {
      final number = rand.nextInt(maxNumber); // max 1000 이하 숫자 랜덤생성
      newNumbers.add(number);
    }

    setState(() {
      randomNumbers = newNumbers.toList();
    });
  }
}

class _Header extends StatelessWidget {
  final VoidCallback onPressed;

  const _Header({required this.onPressed, super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          '랜덤 숫자 생성기',
          style: TextStyle(
            color: Colors.white,
            fontSize: 30.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        IconButton(
          onPressed: onPressed,
          icon: Icon(
            Icons.settings,
            color: RED_COLOR,
          ),
        ),
      ],
    );
  }
}

class _Body extends StatelessWidget {
  final List<int> randomNumbers;

  const _Body({required this.randomNumbers, super.key});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: randomNumbers
            .asMap()
            .entries
            .map(
              (x) => Padding(
                padding: EdgeInsets.only(
                    bottom:
                        x.key == 2 ? 0 : 16.0), // 마지막 7,8,9 아래는 padding을 넣지 않음
                child: NumberRow(
                  number: x.value,
                ),
              ),
            )
            .toList(),
      ),
    );
  }
}

class _Footer extends StatelessWidget {
  final VoidCallback onPressed; // onPress 함수를 외부에서 받아오는 방법 외워둘것

  const _Footer({required this.onPressed, super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity, // width 무한대
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: RED_COLOR,
        ),
        onPressed: onPressed,
        child: Text('생성하기'),
      ),
    );
  }
}
// lib/screen/settings_screen.dart
import 'package:flutter/material.dart';
import 'package:lottery_number_randomly/component/number_row.dart';
import 'package:lottery_number_randomly/constant/color.dart';

class SettingsScreen extends StatefulWidget {
  final int maxNumber;

  const SettingsScreen({required this.maxNumber, super.key});

  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  double maxNumber = 1000;

  @override
  void initState() {
    //
    // TODO: implement initState
    super.initState();
    maxNumber = widget.maxNumber
        .toDouble(); // build() 가 되기 전, home 스크린으로 부터 받은 maxNumber를 변경해준다.
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: PRIMARY_COLOR,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              _Body(maxNumber: maxNumber),
              _Footer(
                  maxNumber: maxNumber,
                  onSliderChanged: onSliderChanged,
                  onButtonPressed: onButtonPressed),
            ],
          ),
        ),
      ),
    );
  }

  void onSliderChanged(double val) {
    setState(() {
      maxNumber =
          val; // slide 값이 변경될때마다 build() 를 호출하여 Slider에게 value 값으로써 넣어줘야 바가 움직인다.
    });
  }

  void onButtonPressed() {
    Navigator.of(context).pop(maxNumber.toInt());
  }
}

class _Body extends StatelessWidget {
  final double maxNumber;

  const _Body({required this.maxNumber, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: NumberRow(
        number: maxNumber.toInt(),
      ),
    );
  }
}

class _Footer extends StatelessWidget {
  final ValueChanged<double>? onSliderChanged;
  final double maxNumber;
  final VoidCallback onButtonPressed;

  const _Footer({
    required this.maxNumber,
    required this.onSliderChanged,
    required this.onButtonPressed,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Slider(
          value: maxNumber,
          min: 1000,
          max: 100000,
          onChanged: onSliderChanged,
        ),
        ElevatedButton(
          onPressed: onButtonPressed,
          style: ElevatedButton.styleFrom(
            backgroundColor: RED_COLOR,
          ),
          child: Text('저장'),
        ),
      ],
    );
  }
}
// lib/component/number_row.dart
import 'package:flutter/material.dart';

class NumberRow extends StatelessWidget {
  final int number;

  const NumberRow({required this.number, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      children: number
          .toString()
          .split('')
          .map(
            (e) => Image.asset(
              'asset/img/$e.png',
              width: 50.0,
              height: 70.0,
            ),
          )
          .toList(),
    );
  }
}