티스토리 뷰
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 패키지를 추가한다.
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 |