티스토리 뷰

무스마/과제

네 번째 과제

하몬드 2023. 8. 21. 11:56

null safety

 

Flutter 2.12 버전부터는 null 관련 버그를 줄이고 코드 안정성을 향상시키기 위해

"null safety" 개념이 도입되었다.

 

- Non-nullable 타입: int, double, String과 같이 null 값을 가질 수 없는 타입은 "non-nullable" 타입으로 분류된다.
변수에는 반드시 초기값이나 값이 할당되어야 합니다.

- Nullable 타입: nullable 타입은 변수가 null 값을 가질 수 있는 상태를 의미한다.

- Null Safety 연산자: Null safety에서는 변수가 null일 가능성이 있는 경우,  ? 연산자를 사용한다.

 

nullsafety는 null로 인해 의도치 않은 동작을 방지하는 것이 목적이다.

 

 

nullable

 

Dart는 변수의 값을 할당하지 않거나 명시적으로 null을 할당할 수 있는 "nullable" 타입을 지원한다.

변수가 값이 없거나 아직 초기화되지 않은 상태를 나타내는 경우 유용하다.

 

nullable형식의 변수는 변수의 타입을 지정하는 글자 앞에 '?'를 붙임으로써 선언할 수 있다.

 

String? str;  // nullable 변수 선언
str = null;   // 명시적으로 null 할당 가능

print(str) // null

 

위 코드에서 String?은 nullable 한 String 타입을 의미하며,

변수 str은 null이나 String 값을 가질 수 있다.

 

 

null check

 

nullable변수 선언 시 nullcheck가 필요하다.

 

nullcheck는 if else문, 삼항연산자 사용, ??연산자 사용

이렇게 3가지 방법으로 할 수 있다.

 

// if else
if (str==null){
    print("empty");
  }else{
    print("str");
  }
  
  // 삼항 연산자
  print(str==null?"empty":str);
  
  // null 병합 연산자
  print(str??"empty");

 

?? 연산자(null 병합 연산자)는 변수가 null일 때 변수를 뒤의 값으로 정의한다.

 

 

late 키워드

 

변수를 당장 초기화 하고 싶진 않은데, 

nullable type으로 선언하고 싶지 도 않을 땐 late키워드를 사용한다.
값이 할당되기 전 해당변수를 읽으면 오류가 발생한다.

 

class Box{
  late String description; 
  
  void setDescription(String str){
    description = str;
  }
}

void main() {
  final myBox = Box();
  myBox.setDescription('This is Box');
  print(myBox.description);
}

 

Box클래스의 description을 Box클래스 내 setDescription으로 초기화해서 출력하는 코드이다.

description은 setDescription에 의해 항상 바뀔 순 있지만 null값은 들어가지 않는다.

 


 

Provider

 

Provider는 플러터 앱에서 복잡한 상태 관리 문제를 해결하고,

앱의 성능을 향상시키는 데에 큰 도움이 되는 강력한 도구이다.

 

Provider는 따로 파일을 작성해 구현할 수도 있고,

provider라는 패키지를 추가해서 쓸 수도 있다.

 

 

파일 작성

 

 

폴더를 이런식으로 구성해 준다.

 

 

counter_provider.dart

 

class Provider {
  int _count = 0;

  int get count => _count;

  void increase() {
    _count++;
  }

  void decrease() {
    _count--;
  }
}

 

만들어둔 provider파일 내에 카운터 앱에 필요한 provider클래스를 작성해 준다.

 

int get count => _count; 는 _count라는 getter 메서드를 정의해 

외부에서 _count 변수의 값을 읽을 수 있도록 한다. 

 

 

main.dart

 

import 'package:flutter/material.dart';
import './provider/counter_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final Provider _provider = Provider();

  MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter App without Provider Package',
      home: CounterScreen(provider: _provider),
    );
  }
}

class CounterScreen extends StatelessWidget {
  final Provider provider;
  const CounterScreen({super.key, required this.provider});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter App')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Count:',
              style: TextStyle(fontSize: 24),
            ),
            Text(
              '${provider.count}',
              style: const TextStyle(fontSize: 48),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    provider.increase();
                    (context as Element).markNeedsBuild();
                  },
                  child: const Text('Increment'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    provider.decrease();
                    (context as Element).markNeedsBuild();
                  },
                  child: const Text('Decrement'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

provider내에 위치한 함수들과 count변수로

카운터 앱을 구현한 것을 볼 수 있다.

 

 

import 'package:flutter/material.dart';
import './provider/counter_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final Provider _provider = Provider();

  MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter App without Provider Package',
      home: CounterScreen(provider: _provider),
    );
  }
}

 

provider 파일을 import 한 뒤,

main.dart에서는 이 Provider 클래스의 인스턴스를 생성하고 CounterScreen 위젯에 전달한다.

CounterScreen에서 생성자로 provider를 초기화한 후 사용한다.

 

 

Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    provider.increase();
                    (context as Element).markNeedsBuild();
                  },
                  child: const Text('Increment'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    provider.decrease();
                    (context as Element).markNeedsBuild();
                  },
                  child: const Text('Decrement'),
                ),
              ],
            ),

 

버튼의 onPressed 프로퍼티에 위치한 코드인

(context as Element).markNeedsBuild();는 상태가 변경되었을 때

해당 위젯을 다시 빌드하도록 하는 코드이다.

setState() 메서드를 호출하여 UI를 다시 빌드하는 것과 유사한 역할을 한다.

 

 

패키지 활용

 

 

provider | Flutter Package

A wrapper around InheritedWidget to make them easier to use and more reusable.

pub.dev

 

 

provider 패키지를 추가한다.

 

 

counter_provider.dart

 

import 'package:flutter/material.dart';

class CountProvider with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increase() {
    _count++;
    notifyListeners();
  }

  void decrease() {
    _count--;
    notifyListeners();
  }
}

 

 

CountProvider 클래스는 ChangeNotifier를 extends 하여 

각 메서드에서 값이 변경될 때마다 notifyListners()로 상태변화를 알린다.

 

notifyListeners()ChangeNotifier 클래스에서 제공하는 메서드로,

상태가 변경되었을 때 해당 상태와 관련된 위젯들에게 알려주어 UI를 업데이트하는 역할을 한다.

 

 

main.dart

 

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './provider/counter_provider.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CountProvider(), // CountProvider 인스턴스 생성
      child: const MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // CountProvider 인스턴스 가져오기
    final countProvider = Provider.of<CountProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider 카운터 예제'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              '카운터 값:',
              style: TextStyle(fontSize: 20),
            ),
            // CountProvider 인스턴스에서 카운터 값을 가져와 표시
            Consumer<CountProvider>(
              builder: (context, provider, child) {
                return Text(
                  '${provider.count}',
                  style: const TextStyle(fontSize: 40),
                );
              },
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FloatingActionButton(
                  onPressed: () {
                    // CountProvider의 decrease 메서드를 호출하여 카운터 값을 감소
                    countProvider.decrease();
                  },
                  child: const Icon(Icons.remove),
                ),
                const SizedBox(width: 20),
                FloatingActionButton(
                  onPressed: () {
                    // CountProvider의 increase 메서드를 호출하여 카운터 값을 증가
                    countProvider.increase();
                  },
                  child: const Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

provider 패키지에 포함된 것들을 활용해 카운터 앱을 구성했다.

 

 

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CountProvider(), // CountProvider 인스턴스 생성
      child: const MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

 

MyApp 클래스에서 ChangeNotifierProvider를 사용하여

CountProvider 인스턴스를 생성하고 앱 전체에 상태 관리를 적용한다.

 

 

@override
  Widget build(BuildContext context) {
    // CountProvider 인스턴스 가져오기
    final countProvider = Provider.of<CountProvider>(context);

 

final countProvider = Provider.of<CountProvider>(context)로

CounterScreen 위젯 내에서 CountProvider 인스턴스를 가져온다.

 

 

Consumer<CountProvider>(
              builder: (context, provider, child) {
                return Text(
                  '${provider.count}',
                  style: const TextStyle(fontSize: 40),
                );
              },
            ),

 

Consumer<CountProvider>CountProvider 클래스의 인스턴스를 구독하고,

상태 변경 시 해당 인스턴스의 count 값을 가져와서 UI에 표시한다.

그리고 UI가 업데이트되어야 할 때만 해당 부분이 다시 그린다.

 

 

children: <Widget>[
                FloatingActionButton(
                  onPressed: () {
                    // CountProvider의 decrease 메서드를 호출하여 카운터 값을 감소
                    countProvider.decrease();
                  },
                  child: const Icon(Icons.remove),
                ),

 

provider 파일에 정의되있는 클래스 내의 메서드에서 상태변화를 알리므로 

버튼을 눌렀을 때 상호작용을 하는 함수 내에 상태변화를 알리는 코드를 넣을 필요가 없어진다.

'무스마 > 과제' 카테고리의 다른 글

무스마 현장실습 종료  (0) 2023.12.31
다섯번째 과제 (4번째 과제 보충)  (0) 2023.09.05
세 번째 과제  (0) 2023.08.13
두 번째 과제  (0) 2023.08.07
첫 번째 과제  (0) 2023.07.31
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함