소개:
Flutter는 Google의 UI 툴킷으로, 모바일, 웹 및 데스크톱용 네이티브 컴파일된 애플리케이션을 단일 코드베이스에서 구축하는 데 사용됩니다. 이식성, 성능 및 사용 편의성으로 개발자들 사이에서 엄청난 인기를 얻고 있습니다. 이 기사에서는 고급 Flutter 개념과 모범 사례에 대해 살펴보고, 여러분의 Flutter 스킬을 향상시키고 견고한 애플리케이션을 구축하는 데 도움이 될 것입니다.
1. 사용자 정의 페인터의 개념을 설명하고 사용자 정의 UI 요소를 생성하는 방법에 대해 설명해주세요.
플러터에서의 사용자 정의 페인터(Custom painters)는 캔버스에 직접 그리는 것으로 복잡하고 사용자 정의된 UI 요소를 생성할 수 있게 해줍니다. 이를 통해 UI 구성 요소의 모양과 동작에 대해 세밀한 제어가 가능해집니다.
예를 들어, 사용자 정의 페인터를 사용하여 서명 패드와 같은 사용자 정의 UI 요소를 만들 수 있습니다:
import 'package:flutter/material.dart';
class SignaturePad extends StatefulWidget {
@override
_SignaturePadState createState() => _SignaturePadState();
}
class _SignaturePadState extends State<SignaturePad> {
List<Offset> _points = <Offset>[];
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox renderBox = context.findRenderObject() as RenderBox;
_points.add(renderBox.globalToLocal(details.globalPosition));
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: CustomPaint(
painter: SignaturePainter(_points),
size: Size.infinite,
),
);
}
}
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset> points;
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
@override
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
이 예제에서 SignaturePad는 캔버스에 선을 그리기 위해 터치 이벤트를 캡처하는 사용자 정의 위젯입니다. CustomPaint 위젯은 SignaturePainter 클래스를 사용하여 캔버스에 그림을 그리며, 이 클래스는 선이 어떻게 그려지는지를 정의합니다.
2. Flutter에서 플랫폼별 기능에 액세스하기 위한 네이티브 모듈을 생성하는 방법을 설명합니다.
Flutter에서 플랫폼별 기능에 액세스하기 위해 네이티브 모듈을 생성하려면 플랫폼 채널을 사용할 수 있습니다. 플랫폼 채널을 통해 Dart 코드와 네이티브 코드 (Android의 경우 Java/Kotlin, iOS의 경우 Objective-C/Swift) 간의 통신이 가능합니다.
다음은 네이티브 토스트 메시지를 표시하는 네이티브 모듈을 생성하는 기본 예시입니다:
먼저 메서드 채널을 정의하겠습니다:
import 'package:flutter/services.dart';
const platform = MethodChannel('example.com/toast');
Future<void> showToast(String message) async {
try {
await platform.invokeMethod('showToast', {'message': message});
} on PlatformException catch (e) {
print("토스트 메시지 표시에 실패했습니다: '${e.message}'.");
}
}
다음으로, 각 플랫폼별로 네이티브 코드를 구현하겠습니다:
안드로이드 (Java/Kotlin):
import android.content.Context;
import android.widget.Toast;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
public class ToastPlugin implements MethodCallHandler {
private final Context context;
private ToastPlugin(Context context) {
this.context = context;
}
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "example.com/toast");
channel.setMethodCallHandler(new ToastPlugin(registrar.context()));
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("showToast")) {
String message = call.argument("message");
showToast(message);
} else {
result.notImplemented();
}
}
private void showToast(String message) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
iOS (Objective-C/Swift):
import Flutter
import UIKit
public class SwiftToastPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "example.com/toast", binaryMessenger: registrar.messenger())
let instance = SwiftToastPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "showToast" {
if let args = call.arguments as? Dictionary<String, Any>,
let message = args["message"] as? String {
showToast(message)
}
} else {
result(FlutterMethodNotImplemented)
}
}
private func showToast(_ message: String) {
if let viewController = UIApplication.shared.keyWindow?.rootViewController {
let toast = UIAlertController(title: nil, message: message, preferredStyle: .alert)
viewController.present(toast, animated: true)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
toast.dismiss(animated: true)
}
}
}
}
AppDelegate(iOS)와 MainActivity(Android)에서 플러그인을 등록해야 합니다.
지금은 showToast 메서드를 호출하여 Dart 코드로 네이티브 토스트 메시지를 표시할 수 있어요.
showToast("Hello, Native Toast!");
이것은 매우 기본적인 예제에 불과하지만, 이 패턴을 확장하여 다양한 플랫폼별 기능에 접근하는 더 복잡한 네이티브 모듈을 만들 수 있어요.
— — —
3. 플러터 앱을 테스트하는 방법을 설명해주세요 (예: 유닛 테스팅, 위젯 테스팅, 통합 테스팅).
플러터 앱을 테스트할 때, 나는 유닛 테스팅, 위젯 테스팅 및 통합 테스팅을 포함한 종합적인 접근 방식을 따릅니다.
* 유닛 테스팅:
각 함수와 클래스에 대한 유닛 테스트를 작성하여 독립적으로 예상대로 작동하는지 확인합니다. 유당 떄 내장된 flutter_test 패키지와 test 패키지를 활용합니다. mockito와 같은 모킹 프레임워크를 사용하여 종속성을 모킹하는 데 도움이 됩니다.
예시:
void main() {
test('String.trim() removes surrounding whitespace', () {
expect(' hello '.trim(), 'hello');
});
}
위젯 테스트:
위젯 테스트는 UI 구성 요소를 격리하여 테스트하는 데 사용됩니다. flutter_test 패키지를 사용하고 WidgetTester를 활용하여 위젯과 그 동작을 검증합니다.
예시:
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
}
* 통합 테스트:
통합 테스트는 앱의 다양한 부분 간 상호 작용을 테스트하는 데 사용됩니다. 저는 플러터 드라이버 패키지와 플러터 테스트 패키지를 이용하여 통합 테스트를 작성합니다. 이러한 테스트는 실제 디바이스나 에뮬레이터에서 실행되며 사용자 상호작용을 시뮬레이트합니다.
예시:
void main() {
group('카운터 앱', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test('카운터를 증가시킵니다', () async {
await driver.tap(find.byType(FloatingActionButton));
expect(await driver.getText(find.text('1')), '1');
});
});
}
이러한 테스트 전략을 개발 프로세스에 통합하여, 내 Flutter 앱을 견고하고 신뢰할 수 있게 만들고 있어요.
4. 고급 Dart 개념인 제네릭, 스트림, 아이솔레이트 및 비동기 프로그래밍에 대해 이야기해보세요.
제네릭:
제네릭은 모든 데이터 유형과 작동할 수 있는 재사용 가능한 코드를 작성할 수 있게 합니다. 컴파일 시간에 타입 안전성을 제공하며 유연한 데이터 구조 및 알고리즘을 생성할 수 있도록 합니다.
예시:
class Box<T> {
T value;
Box(this.value);
}
void main() {
var box = Box<int>(10);
print(box.value); // Output: 10
}
스트림:
스트림은 비동기 데이터 이벤트의 시퀀스를 나타냅니다. 이를 통해 비동기 데이터를 반응적인 방식으로 처리할 수 있습니다. 스트림은 주로 사용자 입력, 네트워크 요청 및 데이터 동기화를 처리하는 데에 사용됩니다.
예시:
import 'dart:async';
void main() {
Stream<int> stream = countStream(5);
stream.listen((int value) {
print(value); // 출력: 0, 1, 2, 3, 4
});
}
Stream<int> countStream(int max) async* {
for (int i = 0; i < max; i++) {
yield i;
}
}
Isolates:
아이솔레이트(Isolates)는 Dart의 동시성 모델로, 코드를 병렬로 실행할 수 있게 합니다. 이들은 서로 독립적이며 메시지 전달을 통해 통신합니다. 아이솔레이트는 비용이 많이 드는 계산, I/O에 바운드된 작업 및 백그라운드 처리에 유용합니다.
예시:
import 'dart:isolate';
void main() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(echo, receivePort.sendPort);
sendReceive(receivePort);
}
void echo(SendPort sendPort) {
ReceivePort receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
print('Received: $message');
});
}
void sendReceive(ReceivePort receivePort) {
SendPort sendPort = await receivePort.first;
sendPort.send('Hello from main!');
}
비동기 프로그래밍:
Dart는 future와 async/await 구문을 통해 비동기 프로그래밍을 지원합니다. Future는 미래의 어느 시점에 사용 가능한 잠재적인 값 또는 오류를 나타냅니다. Async/await는 비동기 코드를 순차적이고 동기적인 스타일로 작성할 수 있는 구문을 제공합니다.
예시:
import 'dart:async';
void main() async {
try {
String result = await fetchUser();
print(result); // Output: User data
} catch (e) {
print('Error: $e');
}
}
Future<String> fetchUser() async {
await Future.delayed(Duration(seconds: 2));
return 'User data';
}
이러한 고급 Dart 개념을 숙지하면 개발자들은 더 효율적이고 확장 가능한 Flutter 애플리케이션을 구축할 수 있습니다.
— — —
5. 당신이 개발한 실제 Flutter 앱이나 구현한 복잡한 기능을 소개해주세요.
한 번 제가 구축한 실제 Flutter 앱은 "TaskMaster"라는 생산성 도구입니다. TaskMaster는 사용자가 업무를 조직화하고 알림을 설정하며 진행 상황을 추적하는 데 도움이 되는 업무 관리 앱입니다.
TaskMaster에 구현한 복잡한 기능 중 하나는 사용자 정의 캘린더 뷰입니다. 캘린더 뷰를 통해 사용자는 월별, 주간 또는 일별 레이아웃에서 업무와 이벤트를 시각화할 수 있습니다. 이 기능은 캘린더에서 직접 업무를 추가/편집하거나 중요도에 따라 색상을 지정하는 기능을 지원하며 Google 캘린더와 같은 외부 캘린더와 동기화할 수 있습니다.
구현에는 각기 다른 캘린더 뷰(월, 주, 일)를 위한 사용자 정의 위젯 생성, 사용자 상호작용(스와이프, 탭) 처리, 플랫폼별 캘린더 API와의 통합이 포함되었습니다.
다음은 사용자 정의 캘린더 뷰의 간소화된 구현 방법입니다:
// 사용자 정의 캘린더 위젯
class CalendarView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
// 캘린더 구현 부분
);
}
}
// 주 앱 위젯
class TaskMasterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('TaskMaster'),
),
body: CalendarView(), // 캘린더 뷰 표시
),
);
}
}
void main() {
runApp(TaskMasterApp());
}
이 예는 단순화된 예시일 뿐이며, 실제 구현에는 더 복잡한 논리가 필요할 것입니다. 작업/이벤트 렌더링, 날짜/시간 계산 처리, 백엔드 서비스 통합 등이 포함될 것입니다.
6. Google Material Design 원칙에 대한 지식 및 Flutter 프로젝트에서 적용하는 방법에 대해 이야기해보세요.
구글 마테리얼 디자인은 구글이 개발한 디자인 언어로, 물성 현실주의, 대담한 그래픽, 의미 있는 모션 등을 강조합니다. 플러터 프로젝트에서는 마테리얼 디자인 원칙을 적용하여 직관적이고 시각적으로 매력적인 사용자 인터페이스를 만들어냅니다.
마테리얼 디자인의 주요 측면과 해당하는 내용은 다음과 같습니다:
마테리얼 구성 요소:
AppBar, BottomNavigationBar, Card, FloatingActionButton 등과 같은 내장 플러터 위젯을 활용하여 마테리얼 디자인 가이드라인에 따라 디자인됩니다.
타이포그래피:
저는 텍스트 요소에 적절한 글꼴 스타일, 크기 및 두께를 사용하여 타이포그래피에 주의를 기울입니다. 더 나은 가독성을 위해 적절한 간격과 정렬을 보장합니다.
색상:
저는 다양한 UI 요소에 색상을 선택하기 위해 Material 색상 팔레트를 사용하여 일관성과 접근성을 보장합니다. 또한 UI에서 깊이와 계층 구조를 만들기 위해 총돌과 그림자를 적용합니다.
동작:
안녕하세요! Flutter의 애니메이션과 트랜지션을 활용하여 섬세한 동작 효과를 추가해 사용자 경험을 향상시키고 피드백을 제공합니다. 페이지 전환, 버튼 클릭 및 입력 유효성 검사를 위한 애니메이션을 포함합니다.
레이아웃:
안녕하세요! 저는 Material Design의 레이아웃 원칙을 따라 그리드, 카드 및 반응형 디자인을 활용하여 시각적으로 조화롭고 일관된 레이아웃을 다양한 화면 크기와 방향에 걸쳐 구축합니다.
Material Design 원칙을 준수하여, Flutter 앱이 통일된 느낌과 사용자들이 익숙하고 쉽게 탐색할 수 있는 완성도 높은 외관을 갖도록 합니다.
7. 디버깅 도구와 기술을 사용하여 Flutter 앱에서 복잡한 문제를 효과적으로 해결하는 방법을 설명하세요.
Flutter 앱에서 복잡한 문제를 해결하기 위해서는 디버깅 도구와 기술의 조합이 필요합니다. 다음은 저의 디버깅 접근 방식입니다:
로깅:
코드 전반에 print 문을 전략적으로 사용하여 중요한 변수, 상태 및 메서드 호출을 로깅합니다. 이를 통해 실행 흐름을 이해하고 잠재적인 문제를 식별하는 데 도움이 됩니다.
디버깅 도구:
Flutter는 강력한 디버깅 도구인 Flutter DevTools를 제공합니다. 이 도구를 사용하면 위젯 트리를 검사하고 로그를 보고 성능을 프로파일링하며 네트워크 요청을 디버깅할 수 있습니다. 또한 Android Studio/VS Code의 Flutter Inspector를 사용하여 UI 요소와 속성을 분석합니다.
중단점:
문제가 발생할 수 있는 중요한 지점에 중단점을 설정합니다. 이를 통해 앱의 실행을 일시 중지하고 변수 값, 스택 추적, 코드를 한 줄씩 실행해볼 수 있습니다.
핫 리로드/리스타트:
플러터의 핫 리로드/리스타트 기능을 활용하여 변경 사항을 빠르게 반복하고 실시간으로 그 효과를 확인합니다. 이를 통해 다양한 시나리오를 테스트하고 문제의 원인을 좁혀낼 수 있습니다.
문제 고립하기:
문제를 체계적으로 분리하기 위해 의심되는 코드를 일시적으로 제거하거나 주석 처리합니다. 이렇게 하면 문제의 범위를 좁히고 원인을 식별하는 데 도움이 됩니다.
문서 읽기:
오류나 예기치 않은 동작을 만나면 공식 Flutter 문서, API 참조, 커뮤니티 포럼을 참고하여 통찰과 해결책을 찾습니다. 다양한 위젯과 API가 어떻게 작동하는지 이해하면 문제 해결에 도움이 될 수 있습니다.
이 디버깅 도구와 기술을 체계적으로 활용하여, Flutter 앱에서 복잡한 문제를 효과적으로 식별하고 해결할 수 있어요.
8. 플랫폼별 기능(예: 카메라 액세스, 알림)을 Flutter 앱에서 어떻게 처리하시겠습니까?
Flutter 앱에서 플랫폼별 기능을 처리하려면, 플랫폼 채널을 사용하여 네이티브 코드(안드로이드의 Java/Kotlin, iOS의 Objective-C/Swift)와 통신할 거예요. 이렇게 카메라 액세스와 알림을 구현할 거예요:
카메라 액세스:
카메라 액세스를 위한 메소드 채널을 정의하세요:
import 'package:flutter/services.dart';
const platform = MethodChannel('example.com/camera');
Future<void> takePicture() async {
try {
await platform.invokeMethod('takePicture');
} on PlatformException catch (e) {
print("사진 찍기에 실패했습니다: '${e.message}'.");
}
}
각 플랫폼에 대한 네이티브 코드를 구현하여 카메라 액세스를 처리하세요.
Android (Java/Kotlin):
import android.content.Context;
import android.content.Intent;
import android.provider.MediaStore;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
public class CameraPlugin implements MethodCallHandler {
private final Context context;
private CameraPlugin(Context context) {
this.context = context;
}
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "example.com/camera");
channel.setMethodCallHandler(new CameraPlugin(registrar.context()));
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("takePicture")) {
takePicture();
} else {
result.notImplemented();
}
}
private void takePicture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
context.startActivity(intent);
}
}
iOS (Objective-C/Swift):
import Flutter
import UIKit
public class SwiftCameraPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "example.com/camera", binaryMessenger: registrar.messenger())
let instance = SwiftCameraPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "takePicture" {
takePicture()
} else {
result(FlutterMethodNotImplemented)
}
}
private func takePicture() {
// Implement camera access for iOS
}
}
알림:
알림을 위한 메서드 채널을 정의하세요:
import 'package:flutter/services.dart';
const platform = MethodChannel('example.com/notifications');
Future<void> showNotification(String message) async {
try {
await platform.invokeMethod('showNotification', {'message': message});
} on PlatformException catch (e) {
print("Failed to show notification: '${e.message}'.");
}
}
각 플랫폼에 대한 기본 코드를 구현하세요.
테이블 태그를 마크다운 형식으로 변경해 보세요.
Flutter 앱의 성능 최적화는 부드럽고 반응성 있는 사용자 경험을 제공하는 데 필수적입니다. 성능 최적화를 위해 사용하는 여러 기술은 다음과 같습니다:
캐싱:
자주 액세스되는 데이터를 로컬에 저장하기 위해 캐싱을 사용하여 반복되는 네트워크 요청을 줄입니다. shared_preferences, hive 또는 sqflite와 같은 라이브러리를 사용하여 이미지, API 응답, 데이터베이스 쿼리 등을 캐싱할 수 있습니다.
지연 로딩:
저는 모든 것을 한꺼번에 로드하는 대신 필요할 때만 데이터를 가져오는 레이지 로딩을 구현했습니다. 이를 통해 초기 앱 로드 시간이 개선되고 메모리 사용량이 줄어듭니다. 페이지별 목록, 무한 스크롤, 자산의 온 디맨드 로딩과 같은 기술들이 레이지 로딩에 대해 흔히 사용됩니다.
효율적인 상태 관리:
Provider, Riverpod 또는 GetX와 같은 효율적인 상태 관리 기술을 사용하여 불필요한 위젯 재구성을 최소화하고 성능을 최적화합니다. UI 상태와 비즈니스 로직을 분리하고 불변 데이터 구조를 사용하여 상태가 변경될 때 UI의 필요한 부분만 업데이트되도록 보장합니다.
최적화된 UI 렌더링:
UI 렌더링을 최적화하기 위해 위젯 수를 최소화하고 위젯 중첩을 줄이며 가능한 경우 const 생성자를 사용하여 위젯 서브트리를 사전에 계산합니다. 이렇게 하면 위젯 빌드 단계에서 소요 시간을 줄이고 UI 반응성을 향상시킬 수 있습니다.
네트워크 최적화:
HTTP 캐싱, 압축, 프리패칭 기술 등을 사용하여 네트워크 요청을 최적화합니다. 이는 특히 네트워크 상황이 좋지 않은 시나리오에서 지연 시간과 대역폭 사용량을 최소화하는 데 도움이 됩니다.
백그라운드 처리:
저는 CPU 집약적인 작업과 오랜 시간이 걸리는 작업을 백그라운드 아이솔레이트로 오프로드하여 주요 UI 스레드를 차단하지 않도록 합니다. 이를 통해 앱이 무거운 계산 또는 I/O 작업 중에도 반응성을 유지할 수 있습니다.
이러한 최적화 기술을 개발 프로세스에 통합함으로써, Flutter 앱이 다양한 기기와 사용 시나리오에서 탁월한 성능을 제공하도록 보장합니다.
10. 선택한 접근 방식(예: BLoC)을 사용하여 복잡한 앱 상태 관리를 어떻게 구현할 것인가요?
제가 플러터에서 복잡한 앱 상태 관리에 자주 사용하는 한 가지 방법은 BLoC (Business Logic Component) 패턴입니다. BLoC는 프레젠테이션 레이어를 비즈니스 로직 및 상태 관리로부터 분리하여 유지 및 테스트를 쉽게 만드는 장점이 있습니다.
아래는 BLoC 패턴을 사용한 복잡한 앱 상태 관리를 구현하는 방법입니다:
BLoC 정의:
import 'dart:async';
import 'package:flutter/material.dart';
enum CounterEvent { increment, decrement }
class CounterBloc {
int _counter = 0;
final _controller = StreamController<int>();
Stream<int> get counterStream => _controller.stream;
void mapEventToState(CounterEvent event) {
if (event == CounterEvent.increment) {
_counter++;
} else {
_counter--;
}
_controller.sink.add(_counter);
}
void dispose() {
_controller.close();
}
}
BLoC 제공하기:
import 'package:flutter/material.dart';
import 'counter_bloc.dart';
class BlocProvider extends InheritedWidget {
final CounterBloc bloc;
BlocProvider({Key key, @required Widget child})
: bloc = CounterBloc(),
super(key: key, child: child);
@override
bool updateShouldNotify(_) => true;
static CounterBloc of(BuildContext context) {
return (context.dependOnInheritedWidgetOfExactType<BlocProvider>()).bloc;
}
}
위젯에서 BLoC 사용하기:
import 'package:flutter/material.dart';
import 'bloc_provider.dart';
import 'counter_bloc.dart';
class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final CounterBloc bloc = BlocProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text('BLoC Pattern Example'),
),
body: StreamBuilder<int>(
stream: bloc.counterStream,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Center(
child: Text('Counter: ${snapshot.data}'),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () => bloc.mapEventToState(CounterEvent.increment),
child: Icon(Icons.add),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () => bloc.mapEventToState(CounterEvent.decrement),
child: Icon(Icons.remove),
),
],
),
);
}
}
BLoC 해제하기:
import 'package:flutter/material.dart';
import 'bloc_provider.dart';
import 'counter_widget.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
child: MaterialApp(
home: CounterWidget(),
),
);
}
}
class BlocProvider extends InheritedWidget {
final CounterBloc bloc;
BlocProvider({Key key, @required Widget child})
: bloc = CounterBloc(),
super(key: key, child: child);
@override
bool updateShouldNotify(_) => true;
static CounterBloc of(BuildContext context) {
return (context.dependOnInheritedWidgetOfExactType<BlocProvider>()).bloc;
}
@override
void dispose() {
bloc.dispose();
super.dispose();
}
}
이 예시에서 CounterBloc은 카운터의 상태를 관리하고 카운터 값의 스트림을 노출합니다. BlocProvider 위젯은 InheritedWidget 메커니즘을 사용하여 CounterBloc을 해당 하위 위젯에 제공합니다. 그러면 위젯은 BlocProvider.of(context) 메서드를 사용하여 CounterBloc에 액세스할 수 있습니다.
BLoC 패턴을 따르면 복잡한 앱 상태를 효과적으로 관리할 수 있고 UI 레이어를 깔끔하고 유지보수 가능하게 유지할 수 있습니다.
더 많은 내용을 보려면 Ahsi Dev를 팔로우하세요...
LinkedIn: https://www.linkedin.com/in/ahsan-saeed-11a787183/