반응형

 

MVVM(Model-View-ViewModel) 패턴은 UI와 비즈니스 로직을 분리하는 소프트웨어 아키텍처 패턴입니다. Flutter에서 MVVM 패턴을 사용하면 코드의 가독성과 유지보수성을 높이고, 테스트 용이성을 개선할 수 있습니다.

 

구성 요소

 

1. Model

데이터 구조와 비즈니스 로직을 포함합니다.

데이터 소스(예: 데이터베이스, API)와 상호작용합니다.

2. View

사용자 인터페이스를 담당합니다.

사용자 입력을 받습니다.

ViewModel에서 제공하는 데이터를 표시합니다.

3. ViewModel

View와 Model 간의 중개자 역할을 합니다.

Model에서 데이터를 가져와서 View에 전달합니다.

사용자 입력을 처리하고, Model을 업데이트합니다.

ChangeNotifier를 상속받아 데이터 변경 시 View에 알립니다.

 

MVVM 패턴의 장점

 

1. 유지보수성 향상: UI와 비즈니스 로직이 분리되어 코드를 더 쉽게 관리할 수 있습니다.

2. 재사용성: ViewModel과 Model은 특정 UI에 의존하지 않으므로 재사용이 가능합니다.

3. 테스트 용이성: UI와 독립적으로 ViewModel과 Model을 테스트할 수 있습니다.

4. 유연성: 다양한 플랫폼과 프레임워크에서 사용 가능합니다.

 

Flutter에서 MVVM 패턴 구현 예제

 

1. Model

 

Model 클래스는 데이터를 정의하고, 데이터와 상호작용하는 로직을 포함합니다.

class User {
  String username;
  String email;

  User({required this.username, required this.email});
}

2. ViewModel

 

ViewModel 클래스는 ChangeNotifier를 상속받아 상태 변화를 알립니다. View와 Model 간의 데이터 처리를 담당합니다.

import 'package:flutter/material.dart';
import 'model/user.dart';

class UserViewModel extends ChangeNotifier {
  User _user = User(username: '', email: '');

  String get username => _user.username;
  String get email => _user.email;

  void setUsername(String username) {
    _user.username = username;
    notifyListeners();
  }

  void setEmail(String email) {
    _user.email = email;
    notifyListeners();
  }
}

3. View

 

View 클래스는 사용자 인터페이스를 정의하고, ViewModel과 상호작용합니다.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'viewmodel/user_viewmodel.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('MVVM Example')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              onChanged: (value) {
                context.read<UserViewModel>().setUsername(value);
              },
              decoration: InputDecoration(labelText: 'Username'),
            ),
            TextField(
              onChanged: (value) {
                context.read<UserViewModel>().setEmail(value);
              },
              decoration: InputDecoration(labelText: 'Email'),
            ),
            SizedBox(height: 20),
            Consumer<UserViewModel>(
              builder: (context, viewModel, child) {
                return Column(
                  children: [
                    Text('Username: ${viewModel.username}'),
                    Text('Email: ${viewModel.email}'),
                  ],
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => UserViewModel(),
      child: MaterialApp(home: MyHomePage()),
    ),
  );
}

요약

 

Model: 데이터 구조와 비즈니스 로직을 정의합니다.

View: 사용자 인터페이스를 담당하며, 사용자 입력을 처리합니다.

ViewModel: Model과 View 간의 중개자로서, 데이터를 처리하고 View에 전달합니다.


다른예시(실습)

 

파일목록
datasource 
- datasource.dart

repository

- album_datasource_repository.dart

viewModel

- album_view_model.dart

view

- album_mvvm_view.dart

 

datasource.dart

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:flutter_lecture/model/album.dart';

class Datasource {

  Future<List<Album>> getAlbumList() async {
    final response = await http
        .get(Uri.parse("https://jsonplaceholder.typicode.com/albums"));

    return jsonDecode(response.body)
        .map<Album>((json) => Album.fromJson(json))
        .toList();
  }

}

 

album_datasource_repository.dart

import 'package:flutter_lecture/datasource/datasource.dart';

import '../model/album.dart';

class AlbumDatasourceRepository {
  final Datasource _datasource = Datasource();


  Future<List<Album>> getAlbumList() {
    return _datasource.getAlbumList();
  }
}

 

album_view_model.dart


import 'package:flutter/cupertino.dart';
import 'package:flutter_lecture/repository/album_datasource_repository.dart';

import '../model/album.dart';

class AlbumViewModel with ChangeNotifier {
  late final AlbumDatasourceRepository _albumRepository;
  List<Album> _albumList = List.empty(growable: true);
  List<Album> get albumList => _albumList;



  AlbumViewModel() {
    _albumRepository = AlbumDatasourceRepository();
    _getAlbumList();
  }

  Future<void> _getAlbumList() async{
    _albumList = await _albumRepository.getAlbumList();
    notifyListeners();
  }

}

 

album_mvvm_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';
import 'package:flutter_lecture/viewModel/album_view_model.dart';
import 'package:provider/provider.dart';

import '../model/album.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<Album> albumList;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<AlbumViewModel>(
        create: ((context) => AlbumViewModel()),
        child: Scaffold(
          appBar: AppBar(
            title: const Text("test title"),
          ),
          body: Consumer<AlbumViewModel>(
            builder: (context, provider, child) {
                albumList = provider.albumList;
                return ListView.builder(
                  itemCount: albumList.length,
                  itemBuilder: (context, index) {
                    return Container(
                      padding: const EdgeInsets.all(10),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text("ID: ${albumList[index].id.toString()}"),
                          Text("Title: ${albumList[index].title}" )
                        ],
                      ),
                    );
                  }
                );
              },
          ),
        ),
    );
  }
}

 

 

참고 자료

 

Flutter 공식 문서

MVVM 패턴

 

이 패턴을 사용하면 Flutter 애플리케이션의 구조를 더 명확하게 만들고, 유지보수와 테스트를 더 쉽게 할 수 있습니다.

반응형
반응형

Flutter에서 Form 데이터를 저장하고 성공 페이지로 이동하는 과정은 다음과 같습니다.

이를 위해 Form, TextFormField, Navigator 등을 사용합니다.

 

단계별 설명

 

1. Form 생성: 사용자로부터 입력을 받을 Form을 생성합니다.

2. Form 데이터 저장: 사용자가 입력한 데이터를 저장합니다.

3. 성공 페이지로 이동: 데이터가 성공적으로 저장되면 성공 페이지로 이동합니다.

 

전체 코드 예제

 

1. main.dart 파일

 

main.dart 파일에서는 초기 화면을 설정하고, 라우트를 정의합니다.

 

import 'package:flutter/material.dart';
import 'form_page.dart';
import 'success_page.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => MyHomePage(),
        '/success': (context) => SuccessPage(),
      },
    );
  }
}

2. form_page.dart 파일

 

form_page.dart 파일에서는 Form을 정의하고, 데이터를 저장한 후 성공 페이지로 이동합니다.

import 'package:flutter/material.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _key = GlobalKey<FormState>();
  late String _username, _email;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Form Page"),
      ),
      body: Container(
        padding: const EdgeInsets.all(15),
        child: Form(
          key: _key,
          child: Column(
            children: [
              usernameInput(),
              const SizedBox(height: 15),
              emailInput(),
              const SizedBox(height: 15),
              submitButton()
            ],
          ),
        ),
      ),
    );
  }

  Widget usernameInput() {
    return TextFormField(
      autofocus: true,
      validator: (val) {
        if (val == null || val.isEmpty) {
          return 'The input is empty';
        } else {
          return null;
        }
      },
      onSaved: (username) => _username = username ?? '',
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your username',
        labelText: 'username',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  Widget emailInput() {
    return TextFormField(
      autofocus: true,
      validator: (val) {
        if (val == null || val.isEmpty) {
          return 'The input is empty';
        } else {
          return null;
        }
      },
      onSaved: (email) => _email = email ?? '',
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your email address',
        labelText: 'email address',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  Widget submitButton() {
    return ElevatedButton(
      onPressed: () {
        if (_key.currentState!.validate()) {
          _key.currentState!.save();
          Navigator.pushNamed(
            context,
            '/success',
            arguments: {'username': _username, 'email': _email},
          );
        }
      },
      child: Container(
        padding: const EdgeInsets.all(15),
        child: const Text('Submit'),
      ),
    );
  }
}

3. success_page.dart 파일

 

success_page.dart 파일에서는 성공적으로 데이터를 저장한 후 이동하는 페이지를 정의합니다.

import 'package:flutter/material.dart';

class SuccessPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final Map<String, String> args =
        ModalRoute.of(context)!.settings.arguments as Map<String, String>;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Success Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("Username: ${args['username']}"),
            Text("Email: ${args['email']}"),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Back to Home'),
            ),
          ],
        ),
      ),
    );
  }
}

주요 단계 설명

 

1. Form 위젯 사용: Form 위젯을 사용하여 사용자 입력을 받습니다. GlobalKey<FormState>를 사용하여 Form의 상태를 관리합니다.

2. Validator 및 onSaved 사용: TextFormField 위젯의 validator 속성을 사용하여 입력 검증을 수행하고, onSaved 속성을 사용하여 데이터를 저장합니다.

3. Navigator 사용: Navigator.pushNamed를 사용하여 성공 페이지로 이동합니다. 이때 arguments를 사용하여 입력된 데이터를 성공 페이지로 전달합니다.

4. 성공 페이지에서 데이터 표시: 성공 페이지에서 ModalRoute.of(context)!.settings.arguments를 사용하여 전달된 데이터를 받아 표시합니다.

 

요약

 

이 과정은 Flutter에서 Form 데이터를 저장하고, 성공 페이지로 이동하는 일반적인 방법을 설명합니다. 이를 통해 사용자로부터 입력을 받고, 입력된 데이터를 다음 페이지로 전달하여 표시할 수 있습니다.

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter scrollview pagination 예제 및 정리  (2) 2024.07.22
flutter mvvm 패턴 정리 및 예제  (0) 2024.07.22
flutter webview 사용방법  (0) 2024.07.22
flutter local notifications - 알림  (0) 2024.07.22
flutter get_it 패턴  (1) 2024.07.22
반응형

Flutter에서 WebView를 사용하는 방법을 정리하겠습니다.

WebView는 애플리케이션 내에서 웹 콘텐츠를 표시할 수 있게 해주는 위젯입니다. 이를 통해 Flutter 앱에서 웹 페이지를 렌더링할 수 있습니다.

 

1. WebView 패키지 추가

 

먼저 webview_flutter 패키지를 pubspec.yaml 파일에 추가합니다.

dependencies:
  webview_flutter: ^4.0.0  # 최신 버전 확인 필요

 

1.2 플랫폼별 설정

iOS (ios/Runner/Info.plist):

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

Android (android/app/build.gradle):

android {
    defaultConfig {
        minSdkVersion 19
    }
}

 

2. 기본 사용법

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewExample extends StatefulWidget {
  @override
  _WebViewExampleState createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<WebViewExample> {
  late WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('WebView Example')),
      body: WebViewWidget(controller: controller),
    );
  }
}

3. 주요 기능

3.1 JavaScript 활성화/비활성화

controller.setJavaScriptMode(JavaScriptMode.unrestricted);

3.2 로딩 프로그레스 모니터링

NavigationDelegate(
  onProgress: (int progress) {
    print('WebView is loading (progress : $progress%)');
  },
)

3.3 페이지 로딩 이벤트

NavigationDelegate(
  onPageStarted: (String url) { print('Page started loading: $url'); },
  onPageFinished: (String url) { print('Page finished loading: $url'); },
)

3.4 에러 처리

NavigationDelegate(
  onWebResourceError: (WebResourceError error) {
    print('Error: ${error.description}');
  },
)

3.5 쿠키 관리

final cookieManager = WebViewCookieManager();
await cookieManager.setCookie(WebViewCookie(name: 'foo', value: 'bar', domain: 'example.com'));

4. 고급 기능

4.1 JavaScript과 Flutter 간 통신

controller.addJavaScriptChannel(
  'Toaster',
  onMessageReceived: (JavaScriptMessage message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message.message)),
    );
  },
);

4.2 사용자 지정 사용자 에이전트

controller.setUserAgent('My Custom User Agent');


NavigationDelegate 예시

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<WebViewExample> createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<WebViewExample> {
  late WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            // 웹 페이지 로딩 진행률 업데이트
            print('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            // 페이지 로딩 시작
            print('Page started loading: $url');
          },
          onPageFinished: (String url) {
            // 페이지 로딩 완료
            print('Page finished loading: $url');
          },
          onWebResourceError: (WebResourceError error) {
            // 웹 리소스 로딩 중 오류 발생
            print('Error: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) {
            // 특정 도메인으로의 이동을 막고 싶을 때 사용
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('Blocking navigation to ${request.url}');
              return NavigationDecision.prevent;
            }
            print('Allowing navigation to ${request.url}');
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(controller: controller),
    );
  }
}

 

NavigationDelegate는 WebViewController를 설정할 때 setNavigationDelegate 메서드를 통해 설정합니다. 일반적으로 WebViewController를 초기화하는 부분에 넣습니다. 주로 initState 메서드 내에서 이루어집니다.

다음은 NavigationDelegate를 올바르게 설정하는 방법을 보여주는 예제입니다:

 
WebView NavigationDelegate 예제
Click to open code

이 예제에서 주목해야 할 점들은 다음과 같습니다:

  1. NavigationDelegate는 WebViewController의 setNavigationDelegate 메서드를 통해 설정됩니다.
  2. initState 메서드 내에서 WebViewController를 초기화하고 설정합니다.
  3. NavigationDelegate는 여러 콜백 함수를 포함할 수 있습니다:
    • onProgress: 페이지 로딩 진행률을 추적합니다.
    • onPageStarted: 페이지 로딩이 시작될 때 호출됩니다.
    • onPageFinished: 페이지 로딩이 완료될 때 호출됩니다.
    • onWebResourceError: 웹 리소스 로딩 중 오류가 발생했을 때 호출됩니다.
    • onNavigationRequest: 새로운 URL로 이동하기 전에 호출됩니다. 여기서 특정 URL로의 이동을 차단할 수 있습니다.
  4. 각 콜백 함수 내에서 원하는 로직을 구현할 수 있습니다. 예를 들어, 로딩 진행률을 UI에 표시하거나, 특정 URL로의 이동을 차단하는 등의 작업을 수행할 수 있습니다.

이렇게 설정된 NavigationDelegate는 WebView의 다양한 이벤트를 처리하고 사용자 경험을 향상시키는 데 도움이 됩니다. 특정 요구사항에 따라 각 콜백 함수의 내용을 커스터마이즈할 수 있습니다.



5. 보안 고려사항

  • HTTPS 사용을 권장합니다.
  • 신뢰할 수 있는 웹사이트만 로드하세요.
  • 필요한 경우에만 JavaScript를 활성화하세요.

6. 성능 최적화

  • 웹뷰는 네이티브 위젯보다 성능이 떨어질 수 있으므로 필요한 경우에만 사용하세요.
  • 복잡한 웹 콘텐츠를 로드할 때는 로딩 인디케이터를 표시하세요.

7. 사용자 경험 개선

  • 뒤로 가기 기능 구현
  • 새로고침 버튼 추가
  • 로딩 진행률 표시

8. 플랫폼별 고려사항

  • iOS와 Android에서 웹뷰의 동작이 약간 다를 수 있으므로 두 플랫폼에서 모두 테스트하세요.
  • 플랫폼별 특정 기능이 필요한 경우 조건부 코드를 사용하세요.
반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter mvvm 패턴 정리 및 예제  (0) 2024.07.22
flutter form 제출 및 라우터 이동  (0) 2024.07.22
flutter local notifications - 알림  (0) 2024.07.22
flutter get_it 패턴  (1) 2024.07.22
flutter provider 패턴  (1) 2024.07.22
반응형

Flutter에서 로컬 알림(Local Notifications)을 사용하면 앱이 백그라운드에 있거나 사용자가 앱을 사용하고 있지 않은 경우에도 사용자에게 알림을 보낼 수 있습니다. 이를 위해 flutter_local_notifications 패키지를 사용할 수 있습니다. 이 패키지는 Android와 iOS 모두에서 로컬 알림을 지원합니다.

 

1. 패키지 설치

 

먼저 pubspec.yaml 파일에 flutter_local_notifications 패키지를 추가합니다:

dependencies:
  flutter:
    sdk: flutter
  flutter_local_notifications: ^12.0.3

그런 다음 flutter pub get 명령을 실행하여 패키지를 설치합니다.

 

2. Android 설정

 

Android에서 로컬 알림을 사용하려면 AndroidManifest.xml 파일에 필요한 권한과 리시버(receiver)를 추가해야 합니다.

 

android/app/src/main/AndroidManifest.xml 파일을 다음과 같이 수정합니다:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.your_app">

    <!-- 알림 관련 권한 -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:label="flutter_lecture"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <!-- 알림 관련 서비스 -->
        <receiver
            android:name="com.dexterous.flutterlocalnotifications.receivers.NotificationBroadcastReceiver"
            android:exported="true"/>
        <receiver
            android:name="com.dexterous.flutterlocalnotifications.receivers.ActionReceiver"
            android:exported="true"/>
        <receiver
            android:name="com.dexterous.flutterlocalnotifications.receivers.DismissedReceiver"
            android:exported="true"/>
        <receiver
            android:name="com.dexterous.flutterlocalnotifications.receivers.ScheduledNotificationBootReceiver"
            android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
            </intent-filter>
        </receiver>

        <!-- 기타 설정들 -->
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            android:showWhenLocked="true"
            android:turnScreenOn="true">
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"/>
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2"/>
    </application>

    <!-- 패키지 가시성 설정 -->
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>

3. iOS 설정

 

iOS에서 로컬 알림을 사용하려면 추가적인 설정이 필요합니다.

 

ios/Runner/Info.plist 파일을 다음과 같이 수정합니다:

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>remote-notification</string>
</array>

4. FlutterLocalNotification 클래스

 

Flutter에서 로컬 알림을 관리할 클래스를 정의합니다. 이 클래스는 알림을 초기화하고, 알림 권한을 요청하며, 알림을 보내는 기능을 포함합니다.

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class FlutterLocalNotification {
  FlutterLocalNotification._();

  static final FlutterLocalNotificationsPlugin flutterLocalNotificationPlugin = FlutterLocalNotificationsPlugin();

  static Future<void> init() async {
    const AndroidInitializationSettings androidInitializationSettings =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    const DarwinInitializationSettings iosInitializationSettings =
        DarwinInitializationSettings(
            requestAlertPermission: false,
            requestBadgePermission: false,
            requestSoundPermission: false
        );

    final InitializationSettings initializationSettings = InitializationSettings(
      android: androidInitializationSettings,
      iOS: iosInitializationSettings,
    );

    await flutterLocalNotificationPlugin.initialize(
      initializationSettings,
      onSelectNotification: (String? payload) async {
        // 알림을 클릭했을 때의 동작 정의
      },
    );
  }

  static void requestNotificationPermission() {
    flutterLocalNotificationPlugin
        .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(alert: true, badge: true, sound: true);
  }

  static Future<void> showNotification() async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
            'channel id',
            'channel name',
            channelDescription: 'channel description',
            importance: Importance.max,
            priority: Priority.high,
            showWhen: false
        );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
      iOS: DarwinNotificationDetails(badgeNumber: 1),
    );

    await flutterLocalNotificationPlugin.show(
      0,
      'test title',
      'test body',
      notificationDetails,
    );
  }
}

 

5. main.dart 파일

 

앱을 초기화하고 로컬 알림을 설정하는 코드를 추가합니다.

import 'package:flutter/material.dart';
import 'package:flutter_lecture/notification.dart'; // 경로를 실제 경로로 변경하세요.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await FlutterLocalNotification.init();  // 플러그인 초기화
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    initializeNotifications();
  }

  Future<void> initializeNotifications() async {
    await FlutterLocalNotification.init();
    Future.delayed(
      const Duration(seconds: 3),
      () => FlutterLocalNotification.requestNotificationPermission()
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("알림 목록"),
      ),
      body: Center(
        child: TextButton(
          onPressed: () => FlutterLocalNotification.showNotification(),
          child: const Text("알림보내기")
        )
      ),
    );
  }
}

6. 프로젝트 정리 및 빌드

 

마지막으로, 프로젝트를 정리하고 다시 빌드합니다.

flutter clean
flutter pub get
flutter run

 

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter form 제출 및 라우터 이동  (0) 2024.07.22
flutter webview 사용방법  (0) 2024.07.22
flutter get_it 패턴  (1) 2024.07.22
flutter provider 패턴  (1) 2024.07.22
flutter bloc 패턴 적용  (0) 2024.07.22
반응형

get_it 패턴은 Flutter 애플리케이션에서 의존성 주입(Dependency Injection)을 간단하고 효율적으로 관리하기 위해 사용되는 패턴입니다. get_it 패키지는 서비스 로케이터(Service Locator) 패턴을 구현하여, 애플리케이션의 상태와 서비스를 전역적으로 접근할 수 있게 합니다.

 

주요 개념

 

1. 서비스 로케이터(Service Locator):

의존성 주입의 한 형태로, 애플리케이션의 다양한 부분에서 공통된 서비스를 쉽게 가져올 수 있게 합니다.

서비스 로케이터를 통해 인스턴스를 전역적으로 관리하고 재사용할 수 있습니다.

2. 의존성 주입(Dependency Injection):

클래스 간의 의존성을 주입하여 코드의 재사용성과 테스트 용이성을 높입니다.

객체를 직접 생성하는 대신, 필요한 의존성을 외부에서 주입합니다.

 

주요 기능

 

등록 및 해제:

서비스를 등록하고 필요 시 해제할 수 있습니다.

싱글톤 및 팩토리:

싱글톤 인스턴스와 팩토리 인스턴스를 지원합니다.

전역 접근:

애플리케이션 어디서나 서비스에 접근할 수 있습니다.

 

예제

 

간단한 예제를 통해 get_it 패턴을 사용하는 방법을 살펴보겠습니다.

 

1. 패키지 추가

 

pubspec.yaml 파일에 get_it 패키지를 추가합니다.

get_it

2. 서비스 클래스 정의

class CounterService {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
  }

  void decrement() {
    _count--;
  }
}

3. 서비스 등록

 

애플리케이션의 초기화 단계에서 서비스를 등록합니다.

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'counter_service.dart';

void setupLocator() {
  GetIt.I.registerLazySingleton(() => CounterService());
}

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

4. 서비스 사용

 

애플리케이션의 다양한 부분에서 서비스를 사용합니다.

 

import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'counter_service.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  final CounterService _counterService = GetIt.I<CounterService>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Get It Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Counter: ${_counterService.count}',
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  onPressed: _counterService.increment,
                  child: Icon(Icons.add),
                ),
                SizedBox(width: 20),
                FloatingActionButton(
                  onPressed: _counterService.decrement,
                  child: Icon(Icons.remove),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

 

 

1. 서비스 등록:

GetIt.I.registerLazySingleton(() => CounterService());를 사용하여 서비스를 등록합니다. 이는 싱글톤 인스턴스를 생성합니다.

registerFactory를 사용하면 요청할 때마다 새로운 인스턴스를 생성합니다.

2. 서비스 사용:

GetIt.I<CounterService>()를 사용하여 등록된 서비스를 가져옵니다.

이를 통해 애플리케이션의 어디서나 동일한 서비스 인스턴스를 사용할 수 있습니다.

 

 

다른 예시 (실습)

파일구성

locator 패키지

- locator.dart

service 패키지
- album_service.dart 

view 패키지
- album_getit_view.dart
메인 메소드 
- locator 메소드 호출

 

album_service.dart

import 'dart:convert';

import '../model/album.dart';
import 'package:http/http.dart' as http;

abstract class AlbumService {
  Future<List<Album>> fetchAlbums();
}


class AlbumServiceImplementation implements AlbumService {
  @override
  Future<List<Album>> fetchAlbums() async {
    final reponse = await http.get(
        Uri.parse("https://jsonplaceholder.typicode.com/albums")
    );
    final List<Album> result = jsonDecode(reponse.body)
        .map<Album>((json) => Album.fromJson(json))
        .toList();

    return result;
  }
  
}

locator.dart

import 'package:flutter_lecture/service/album_service.dart';
import 'package:get_it/get_it.dart';

GetIt locator = GetIt.instance;

initLocator() {
  locator.registerLazySingleton<AlbumService>(() => AlbumServiceImplementation());

}

 

album_getit_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_lecture/locator/locator.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';
import 'package:flutter_lecture/service/album_service.dart';

import '../model/album.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final AlbumService _service = locator<AlbumService>();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("test title"),
      ),
      body: FutureBuilder(
        future: _service.fetchAlbums(),
        builder: (context, snapshot) {
          if(snapshot.hasData) {
            List<Album>? list = snapshot.data;
            return ListView.builder(
                itemCount: list?.length,
                itemBuilder: (context, index) {
                  return Container(
                    padding: const EdgeInsets.all(10),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text("ID: ${list?[index].id.toString()}"),
                        Text("Title: ${list?[index].title}" )
                      ],
                    ),
                  );
                }
            );
          } else if(snapshot.hasError){
            return Center(
              child: Text(snapshot.error.toString()),
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(
                strokeWidth: 2,
              ),
            );
          }
        },
      ),
    );
  }
}

 

메인 메소드 : 

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

 

 

장점

 

간단함:

의존성 주입을 간단하게 구현할 수 있습니다.

유연성:

다양한 유형의 서비스 인스턴스를 등록하고 관리할 수 있습니다.

테스트 용이성:

의존성을 쉽게 주입할 수 있어 단위 테스트를 쉽게 작성할 수 있습니다.

 

참고 문서

 

Get It 패키지 문서

Flutter 공식 의존성 주입 가이드

 

이 정보를 통해 Flutter 애플리케이션에서 get_it 패턴을 사용하여 의존성 주입을 효율적으로 관리할 수 있습니다. get_it 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter webview 사용방법  (0) 2024.07.22
flutter local notifications - 알림  (0) 2024.07.22
flutter provider 패턴  (1) 2024.07.22
flutter bloc 패턴 적용  (0) 2024.07.22
flutter 리프레쉬 인디케이터  (0) 2024.07.22
반응형

Provider 패턴은 Flutter 애플리케이션에서 상태 관리를 효율적으로 수행하기 위해 사용되는 패턴입니다. 이 패턴은 InheritedWidget을 기반으로 하며, 상태를 위젯 트리에서 쉽게 공유하고 관리할 수 있게 도와줍니다. Provider 패턴은 Flutter의 공식 상태 관리 솔루션 중 하나로, 단순하고 강력한 상태 관리 방법을 제공합니다.

 

주요 개념

 

1. Provider:

상태를 제공하는 역할을 합니다. Provider는 다양한 유형이 있으며, ChangeNotifierProvider, Provider, FutureProvider, StreamProvider 등이 있습니다.

2. ChangeNotifier:

상태 변경을 알리기 위해 사용되는 클래스입니다. ChangeNotifier를 상속받아 상태를 관리하고, notifyListeners()를 호출하여 리스너들에게 상태 변경을 알립니다.

3. Consumer:

Provider에서 제공하는 상태를 구독하고, 상태가 변경될 때마다 UI를 업데이트하는 역할을 합니다.

 

Provider의 유형

 

1. ChangeNotifierProvider:

ChangeNotifier를 사용하여 상태를 제공하고 관리합니다.

2. Provider:

기본적인 상태 제공자로, 변경되지 않는 데이터를 제공할 때 사용합니다.

3. FutureProvider:

비동기 작업의 결과를 제공하는 Provider입니다.

4. StreamProvider:

스트림 데이터를 제공하는 Provider입니다.

 

예제: ChangeNotifierProvider 사용

 

간단한 카운터 애플리케이션을 Provider 패턴으로 구현한 예제를 살펴보겠습니다.


dependencies 에 추가

provider:

 

모델 클래스

import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

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

메인 애플리케이션

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // Counter 모델 클래스 가져오기

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counter()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Counter Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Counter: ${counter.count}',
              style: TextStyle(fontSize: 24),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: counter.increment,
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: counter.decrement,
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

주요 구성 요소

 

1. Counter 모델 클래스:

ChangeNotifier를 상속받아 상태를 관리합니다.

incrementdecrement 메서드를 통해 상태를 변경하고, notifyListeners()를 호출하여 리스너에게 상태 변경을 알립니다.

2. MultiProvider:

여러 Provider를 동시에 제공할 때 사용합니다. 이 예제에서는 ChangeNotifierProvider를 사용하여 Counter 상태를 제공합니다.

3. Provider.of(context):

현재 컨텍스트에서 Provider가 제공하는 상태를 구독합니다. 여기서는 Counter 상태를 구독하여 카운터 값을 업데이트합니다.

4. FloatingActionButton:

onPressed 콜백에서 counter.incrementcounter.decrement를 호출하여 카운터 값을 증가 및 감소시킵니다.

 

 

 

다른 예시(실습)

필요 파일
provider 패키지
- album_provider.dart
model 패키지
- album.dart

view 패키지

- album_provider_view.dart

 

album.dart

class Album {
  int? userId;
  int? id;
  String? title;

  Album({this.userId, this.id, this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

 

album_provider.dart

import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;

import '../model/album.dart';

class AlbumProvider with ChangeNotifier {
  final List<Album> _albumList = List.empty(growable: true);

  List<Album> getAlbumList() {
    _fetchAlbums();
    return _albumList;
  }

  void _fetchAlbums() async {
    final response = await http.get(Uri.parse("https://jsonplaceholder.typicode.com/albums"));
    final List<Album> result = jsonDecode(response.body)
        .map<Album>((json) => Album.fromJson(json))
        .toList();
    _albumList.clear();
    _albumList.addAll(result);
    notifyListeners();
  }
}

album_provider_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_lecture/provider/album_provider.dart';
import 'package:provider/provider.dart';

import '../model/album.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  late List<Album> albumList;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<AlbumProvider>(
      create: ((context) => AlbumProvider()),
      child: Scaffold(
        appBar: AppBar(
          title: const Text("test title"),
        ),
        body: Consumer<AlbumProvider>(
          builder: (context, provider, child) {
            albumList = provider.getAlbumList();
            return ListView.builder(
                itemCount: albumList.length,
                itemBuilder: (context, index) {
                  return Container(
                    padding: const EdgeInsets.all(10),
                    child: Text("${albumList[index].id} : ${albumList[index].title}"),
                  );
                }
            );
          },
        ),
      ),
    );
  }
}

 

 

 

Provider 패턴의 장점

 

1. 단순성:

Provider 패턴은 Flutter의 상태 관리 솔루션 중 가장 단순하면서도 강력한 패턴입니다.

2. 유연성:

다양한 유형의 Provider를 제공하여 다양한 상태 관리 요구를 충족할 수 있습니다.

3. 성능 최적화:

Provider는 효율적으로 상태를 구독하고 업데이트하여 성능을 최적화합니다.

4. 재사용성:

상태와 비즈니스 로직을 별도의 클래스로 분리하여 재사용성을 높입니다.

 

참고 문서

 

Provider 패키지 문서

Flutter 공식 상태 관리 문서

 

이 정보를 통해 Flutter 애플리케이션에서 Provider 패턴을 효과적으로 사용하여 상태 관리를 수행할 수 있습니다. Provider 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter local notifications - 알림  (0) 2024.07.22
flutter get_it 패턴  (1) 2024.07.22
flutter bloc 패턴 적용  (0) 2024.07.22
flutter 리프레쉬 인디케이터  (0) 2024.07.22
dart 반복문 정리  (0) 2024.07.21
반응형

BLoC(Business Logic Component) 패턴은 Flutter 애플리케이션에서 비즈니스 로직을 UI 코드와 분리하는 데 사용되는 설계 패턴입니다.

이 패턴은 StreamSink를 활용하여 데이터의 흐름을 관리하고, 상태 관리를 효율적으로 수행할 수 있게 합니다.

BLoC 패턴은 Flutter의 공식 상태 관리 솔루션 중 하나로, 재사용성과 테스트 가능성을 높이는 데 기여합니다.

 

 

주요 개념

 

1. BLoC (Business Logic Component):

비즈니스 로직을 담당하는 컴포넌트입니다. Stream을 통해 UI에 데이터를 전달하고, Sink를 통해 UI로부터 이벤트를 받습니다.

2. Stream:

비동기 데이터의 흐름을 나타내는 Dart의 클래스입니다. BLoC 패턴에서는 상태 변경을 UI로 전달하는 데 사용됩니다.

3. Sink:

데이터를 Stream에 전달하는 입력 통로입니다. BLoC 패턴에서는 UI에서 발생한 이벤트를 BLoC로 전달하는 데 사용됩니다.

4. Event:

UI에서 발생하는 사용자 상호작용을 나타냅니다. 버튼 클릭, 폼 입력 등 다양한 이벤트가 포함됩니다.

5. State:

UI에서 표시할 데이터를 나타냅니다. BLoC는 상태를 관리하고 Stream을 통해 UI로 전달합니다.

 

BLoC 패턴의 구성 요소

 

1. Event 클래스:

사용자가 수행하는 동작을 정의합니다.

2. State 클래스:

애플리케이션의 상태를 정의합니다.

3. BLoC 클래스:

이벤트를 처리하고 상태를 관리하는 로직을 포함합니다.

 

예제

 

간단한 카운터 애플리케이션을 BLoC 패턴으로 구현한 예제를 살펴보겠습니다.

 

Event 클래스

abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}

 

State 클래스

class CounterState {
  final int count;

  CounterState(this.count);
}

 

BLoC 클래스

import 'dart:async';

class CounterBloc {
  final _stateController = StreamController<CounterState>();
  StreamSink<CounterState> get _inCounter => _stateController.sink;
  Stream<CounterState> get counter => _stateController.stream;

  final _eventController = StreamController<CounterEvent>();
  Sink<CounterEvent> get counterEventSink => _eventController.sink;

  CounterBloc() {
    _eventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CounterEvent event) {
    if (event is Increment) {
      _inCounter.add(CounterState(_stateController.stream.value.count + 1));
    } else if (event is Decrement) {
      _inCounter.add(CounterState(_stateController.stream.value.count - 1));
    }
  }

  void dispose() {
    _stateController.close();
    _eventController.close();
  }
}

 

UI 코드

import 'package:flutter/material.dart';
import 'counter_bloc.dart'; // BLoC 파일을 가져옴

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  final CounterBloc _bloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLoC Counter Example'),
      ),
      body: Center(
        child: StreamBuilder<CounterState>(
          stream: _bloc.counter,
          initialData: CounterState(0),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return CircularProgressIndicator();
            }
            return Text(
              'Counter: ${snapshot.data.count}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              _bloc.counterEventSink.add(Increment());
            },
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () {
              _bloc.counterEventSink.add(Decrement());
            },
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

 

예시(실습)

 

 

패키지 추가

rxdart:
http:



model 부터 생성해줍니다.

class Album {
  int? userId;
  int? id;
  String? title;

  Album({this.userId, this.id, this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}
import './album.dart';

class Albums {
  late List<Album> albums;

  Albums({required this.albums});

  Albums.fromJSON(List<dynamic> json) {
    albums = List<Album>.empty(growable: true);
    for (dynamic val in json) {
      albums.add(Album.fromJson(val));
    }
  }
}

 

provider 를 생성해줍니다.

import 'dart:convert';

import 'package:http/http.dart' show Client;
import 'package:flutter_lecture/model/albums.dart';


class AlbumApiProvider {
  Client client = Client();
  
  Future<Albums> fetchAlbumList() async {
    final response = await client
        .get(Uri.parse("https://jsonplaceholder.typicode.com/albums"));

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return Albums.fromJSON(data);
    } else {
      throw Exception("Failed");
    }
  }
}

 

repository 를 생성합니다.

import 'package:flutter_lecture/data_provider/api_provider.dart';
import 'package:flutter_lecture/model/albums.dart';

class AlbumRepository {

  final AlbumApiProvider _albumApiProvider = AlbumApiProvider();

  Future<Albums> fetchAllAlbums() async => _albumApiProvider.fetchAlbumList();
  
}

 

bloc 파일을 만들어줍니다. 

import 'package:flutter_lecture/repository/ablum_repository.dart';
import 'package:rxdart/rxdart.dart';

import '../model/albums.dart';

class AlbumBloc {
  final AlbumRepository _albumRepository = AlbumRepository();
  final PublishSubject<Albums> _albumFetcher = PublishSubject<Albums>();

  Stream<Albums> get allAlbums => _albumFetcher.stream;

  Future<void> fetchAllAlbums() async {
    Albums albums = await _albumRepository.fetchAllAlbums();
    _albumFetcher.sink.add(albums);
  }

  dispose() {
    _albumFetcher.close();
  }
}

 

bloc 파일을 적용할 view 파일을 마지막으로 만들어줍니다.

import 'package:flutter/material.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final AlbumBloc _albumBloc = AlbumBloc();


  @override
  void initState() {
    _albumBloc.fetchAllAlbums();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("test title"),
      ),
      body: StreamBuilder<Albums>(
        stream: _albumBloc.allAlbums,
        builder: (context, snapshot) {
          if(snapshot.hasData) {
            Albums? albumList = snapshot.data;
            return ListView.builder(
              itemCount: albumList?.albums.length,
              itemBuilder: (context, index) {
                return Container(
                  padding: const EdgeInsets.all(10),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text("ID: ${albumList?.albums[index].id.toString()}"),
                      Text("Title: ${albumList?.albums[index].title}" )
                    ],
                  ),
                );
              }
            );
          } else if(snapshot.hasError){
              return Center(
                child: Text(snapshot.error.toString()),
              );

          } else {
            return const Center(
              child: CircularProgressIndicator(
                strokeWidth: 2,
              ),
            );
          }
        },
      ),
    );
  }
}

 

 

BLoC 패턴의 장점

 

1. UI와 비즈니스 로직의 분리:

UI 코드와 비즈니스 로직을 명확히 분리하여 코드의 가독성과 유지보수성을 높입니다.

2. 재사용성과 테스트 용이성:

BLoC 컴포넌트는 독립적으로 동작하므로 다른 프로젝트나 화면에서 재사용할 수 있습니다. 또한, 비즈니스 로직이 UI와 분리되어 있어 단위 테스트가 용이합니다.

3. 비동기 데이터 처리:

StreamSink를 사용하여 비동기 데이터 처리를 효과적으로 관리할 수 있습니다.

 

참고 문서

 

Bloc 패턴 공식 문서

Flutter의 상태 관리

Dart의 Stream 클래스 문서

 

이 정보를 통해 Flutter 애플리케이션에서 BLoC 패턴을 효과적으로 사용하여 상태 관리를 수행할 수 있습니다. BLoC 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter get_it 패턴  (1) 2024.07.22
flutter provider 패턴  (1) 2024.07.22
flutter 리프레쉬 인디케이터  (0) 2024.07.22
dart 반복문 정리  (0) 2024.07.21
dart 조건문 정리  (0) 2024.07.21
반응형

RefreshIndicator

RefreshIndicator는 Flutter에서 사용자가 목록을 끌어 당겨서 새로 고침(pull-to-refresh) 동작을 수행할 수 있게 하는 위젯입니다. 주로 스크롤 가능한 목록에서 사용되며, 사용자에게 데이터를 새로 고치는 기능을 제공합니다.

주요 속성

  • child: RefreshIndicator가 감싸는 스크롤 가능한 위젯입니다. 일반적으로 ListView와 같은 위젯이 사용됩니다.
  • onRefresh: 새로 고침 동작이 발생할 때 호출되는 콜백 함수입니다. 이 함수는 Future<void>를 반환하여 비동기 작업을 수행할 수 있습니다.
  • color: 인디케이터의 색상을 지정합니다.
  • backgroundColor: 인디케이터의 배경색을 지정합니다.
  • displacement: 인디케이터가 표시될 때 위쪽에서 떨어진 거리입니다.
  • strokeWidth: 인디케이터의 선 두께입니다.

사용 예제

아래는 RefreshIndicator를 사용하여 목록을 새로 고침하는 예제입니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('RefreshIndicator Example'),
        ),
        body: RefreshIndicatorExample(),
      ),
    );
  }
}

class RefreshIndicatorExample extends StatefulWidget {
  @override
  _RefreshIndicatorExampleState createState() => _RefreshIndicatorExampleState();
}

class _RefreshIndicatorExampleState extends State<RefreshIndicatorExample> {
  final List<String> _items = List.generate(20, (index) => 'Item ${index + 1}');

  Future<void> _refresh() async {
    await Future.delayed(Duration(seconds: 2)); // 2초 동안 대기

    setState(() {
      _items.shuffle(); // 아이템을 섞어서 목록을 새로 고침
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_items[index]),
          );
        },
      ),
    );
  }
}

 

주요 포인트

  1. RefreshIndicator 사용:
    • RefreshIndicator 위젯은 스크롤 가능한 위젯을 감싸서 사용합니다.
    • onRefresh 콜백은 데이터를 새로 고침하는 로직을 포함해야 하며, Future<void>를 반환해야 합니다.
  2. 데이터 새로 고침:
    • onRefresh 콜백 내에서 비동기 작업을 수행하여 데이터를 새로 고칩니다.
    • 비동기 작업이 완료되면 setState를 호출하여 UI를 업데이트합니다.
  3. UI 업데이트:
    • setState를 사용하여 데이터 변경 사항을 반영하고 UI를 업데이트합니다.

추가 예제: 커스텀 인디케이터

기본적인 사용 예제 외에도 RefreshIndicator의 속성을 사용하여 커스텀 인디케이터를 만들 수 있습니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom RefreshIndicator Example'),
        ),
        body: CustomRefreshIndicatorExample(),
      ),
    );
  }
}

class CustomRefreshIndicatorExample extends StatefulWidget {
  @override
  _CustomRefreshIndicatorExampleState createState() => _CustomRefreshIndicatorExampleState();
}

class _CustomRefreshIndicatorExampleState extends State<CustomRefreshIndicatorExample> {
  final List<String> _items = List.generate(20, (index) => 'Item ${index + 1}');

  Future<void> _refresh() async {
    await Future.delayed(Duration(seconds: 2));

    setState(() {
      _items.shuffle();
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      color: Colors.white,
      backgroundColor: Colors.blue,
      displacement: 40,
      strokeWidth: 3,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_items[index]),
          );
        },
      ),
    );
  }
}

 

dio 를 이용한 데이터 가져오기 예시 (실습)

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

import 'product.dart';

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final Dio dio = Dio();

  Future<List<Product>> getProductData() async {
    try {
      print("데이터 가져오기 시작");
      var response = await dio.get("https://dummyjson.com/products");
      List<Product> products = (response.data['products'] as List)
          .map<Product>((json) => Product.fromJson(json))
          .toList();
      print("데이터 가져오기 완료");
      return products;
    } catch (e) {
      print("오류 발생: $e");
      return []; // 에러 발생 시 빈 리스트 반환
    }
  }

  Future<void> refreshData() async {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("제품 목록"),
      ),
      body: RefreshIndicator(
        onRefresh: refreshData,
        child: FutureBuilder<List<Product>>(
          future: getProductData(),
          builder: (BuildContext context, AsyncSnapshot<List<Product>> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: CircularProgressIndicator());
            } else if (snapshot.hasError) {
              return Center(child: Text('오류 발생: ${snapshot.error}'));
            } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
              return const Center(child: Text('데이터가 없습니다'));
            } else {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (BuildContext context, int index) {
                  var data = snapshot.data![index];
                  return Container(
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      border: Border.all(width: 1, color: Colors.black12),
                    ),
                    child: Text("${data.title} (${data.description})"),
                  );
                },
              );
            }
          },
        ),
      ),
    );
  }
}

결론

RefreshIndicator는 Flutter에서 사용자가 목록을 끌어 당겨서 새로 고침 동작을 수행할 수 있게 하는 유용한 위젯입니다. 이 위젯을 사용하면 사용자 경험을 향상시키고, 데이터 업데이트를 쉽게 처리할 수 있습니다. RefreshIndicator를 사용하여 간단한 pull-to-refresh 기능을 구현하고, 다양한 속성을 통해 커스터마이징할 수 있습니다.

참고 문서

반응형

'프론트엔드 > Flutter' 카테고리의 다른 글

flutter provider 패턴  (1) 2024.07.22
flutter bloc 패턴 적용  (0) 2024.07.22
dart 반복문 정리  (0) 2024.07.21
dart 조건문 정리  (0) 2024.07.21
dart collection 설명 및 예시  (0) 2024.07.21

+ Recent posts