반응형

Flutter는 다양한 디자인 패턴을 사용하여 애플리케이션 개발을 더욱 구조적이고 유지보수가 쉽게 만들 수 있습니다. 주요 패턴들을 정리해보겠습니다.

 

1. Provider 패턴

 

설명: Flutter 애플리케이션에서 상태 관리를 간단하고 효율적으로 수행하기 위해 사용됩니다. InheritedWidget을 기반으로 하여, 상위 위젯 트리에서 상태를 하위 위젯 트리에 전달할 수 있습니다.

사용법: provider 패키지를 사용하여 구현합니다.

예제:

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

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

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Provider Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Consumer<Counter>(
                builder: (context, counter, _) {
                  return Text(
                    '${counter.count}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            Provider.of<Counter>(context, listen: false).increment();
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

2. BLoC (Business Logic Component) 패턴

 

설명: BLoC 패턴은 비즈니스 로직을 UI에서 분리하여 유지보수성과 테스트 용이성을 높입니다. 이벤트와 상태 스트림을 사용하여 비동기 데이터 흐름을 관리합니다.

사용법: flutter_bloc 패키지를 사용하여 구현합니다.

예제:

class CounterEvent {}

class CounterState {
  final int counter;
  CounterState(this.counter);
}

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0));

  @override
  Stream<CounterState> mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.counter + 1);
    }
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) => CounterBloc(),
        child: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC Example')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text(
              '${state.counter}',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          BlocProvider.of<CounterBloc>(context).add(IncrementEvent());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

3. Redux 패턴

 

설명: 상태 관리 라이브러리로, 애플리케이션의 상태를 하나의 중앙 저장소에서 관리합니다. 액션을 통해 상태를 업데이트하고 리듀서를 통해 새로운 상태를 반환합니다.

사용법: flutter_redux 패키지를 사용하여 구현합니다.

예제:

// State
class AppState {
  final int counter;
  AppState(this.counter);
}

// Actions
class IncrementAction {}

// Reducer
AppState counterReducer(AppState state, action) {
  if (action is IncrementAction) {
    return AppState(state.counter + 1);
  }
  return state;
}

// Store
final store = Store<AppState>(
  counterReducer,
  initialState: AppState(0),
);

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Redux Example')),
      body: Center(
        child: StoreConnector<AppState, String>(
          converter: (store) => store.state.counter.toString(),
          builder: (context, counter) {
            return Text(
              counter,
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          StoreProvider.of<AppState>(context).dispatch(IncrementAction());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

4. MVVM (Model-View-ViewModel) 패턴

 

설명: 애플리케이션의 UI를 모델과 분리하여 유지보수성과 테스트 용이성을 높입니다. ViewModel은 모델 데이터를 처리하고 이를 뷰에 바인딩합니다.

사용법: providerChangeNotifier를 사용하여 구현합니다.

예제:

class CounterViewModel extends ChangeNotifier {
  int _counter = 0;
  int get counter => _counter;

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterViewModel(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('MVVM Example')),
      body: Center(
        child: Consumer<CounterViewModel>(
          builder: (context, viewModel, _) {
            return Text(
              '${viewModel.counter}',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<CounterViewModel>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

참고 자료

 

Flutter Documentation - Provider

Flutter Documentation - BLoC

Redux in Flutter

MVVM in Flutter

반응형
반응형

Dart의 Futureasync/await는 비동기 프로그래밍을 지원하는 핵심 개념입니다.

이들 개념을 사용하면 비동기 작업을 더 쉽게 작성하고 관리할 수 있습니다.

 

Future

 

Future는 비동기 작업의 결과를 나타내는 객체입니다. 이 객체는 작업이 완료되었을 때 결과를 제공하거나 오류를 전달합니다. Future는 JavaScript의 Promise와 유사합니다.

 

Future 사용 예제

 

1. Future 생성

Future 사용 예제

 

1. Future 생성

Future<String> fetchUserOrder() {
  // 2초 후에 'Order Complete' 반환
  return Future.delayed(Duration(seconds: 2), () => 'Order Complete');
}

 

2. then() 사용

void main() {
  fetchUserOrder().then((order) {
    print(order); // 'Order Complete' 출력
  }).catchError((error) {
    print('Error: $error');
  });
}

async 및 await

 

asyncawait 키워드는 비동기 코드를 더 읽기 쉽게 작성할 수 있도록 도와줍니다. async 함수는 Future를 반환하며, await 키워드는 Future가 완료될 때까지 기다린 후 결과를 반환합니다.

 

async 및 await 사용 예제

 

1. async 함수 정의

 

Future<void> fetchUserOrder() async {
  try {
    String order = await Future.delayed(Duration(seconds: 2), () => 'Order Complete');
    print(order); // 'Order Complete' 출력
  } catch (e) {
    print('Error: $e');
  }
}

2. async 함수 호출

void main() {
  fetchUserOrder();
  print('Fetching user order...'); // 먼저 출력됨
}

 

자세한 설명

 

1. Future

비동기 작업을 표현: Future는 비동기 작업의 완료(성공/실패)를 표현합니다.

메서드:

then: Future가 완료될 때 호출될 콜백을 등록합니다.

catchError: Future가 실패할 때 호출될 콜백을 등록합니다.

whenComplete: Future가 완료되면 성공 여부와 상관없이 호출될 콜백을 등록합니다.

2. async

함수를 비동기로 표시: 함수에 async를 붙이면 해당 함수는 Future를 반환합니다.

비동기 코드 작성: async 키워드를 사용하면 코드가 비동기로 실행되며, 함수는 즉시 Future를 반환합니다.

3. await

Future가 완료될 때까지 기다림: await 키워드를 사용하면 Future가 완료될 때까지 기다리고, 완료 후 결과를 반환합니다.

try-catch와 함께 사용: 비동기 작업에서 발생하는 오류를 처리하기 위해 try-catch 블록과 함께 사용할 수 있습니다.

 

 

참고

https://dart-ko.dev/codelabs/async-await

반응형
반응형

 

API(Application Programming Interface)는 소프트웨어 애플리케이션 간의 상호 작용을 가능하게 하는 인터페이스입니다.

API는 다양한 기능을 다른 프로그램에서 사용할 수 있도록 정의된 규칙 및 도구의 집합을 제공합니다.

 

 

주요 개념

 

1. 인터페이스: API는 소프트웨어 구성 요소가 서로 상호작용할 수 있도록 하는 인터페이스입니다. 이 인터페이스는 명령, 함수, 프로토콜 등으로 구성될 수 있습니다.

2. 표준화: API는 특정 작업을 수행하기 위한 표준화된 방법을 제공합니다. 이를 통해 다양한 소프트웨어가 일관된 방식으로 상호작용할 수 있습니다.

3. 재사용성: 개발자는 API를 사용하여 기존 기능을 재사용할 수 있습니다. 새로운 애플리케이션을 개발할 때 처음부터 모든 기능을 구현할 필요 없이, API를 통해 필요한 기능을 빠르게 통합할 수 있습니다.

 

종류

 

1. 웹 API: 웹 서비스 상호작용을 위한 API로, HTTP 프로토콜을 사용하여 데이터를 주고받습니다.

RESTful API와 SOAP API가 대표적입니다.

RESTful API: REST(Representational State Transfer) 아키텍처 스타일을 따르는 API로, 자원을 URL로 표현하고 HTTP 메서드(GET, POST, PUT, DELETE 등)를 통해 조작합니다.

SOAP API: SOAP(Simple Object Access Protocol)를 사용하는 API로, XML 형식을 통해 메시지를 교환합니다.

2. 라이브러리 API: 소프트웨어 라이브러리에서 제공하는 API로, 특정 프로그래밍 언어로 작성된 함수와 절차를 사용하여 기능을 구현합니다.

3. 운영 체제 API: 운영 체제가 제공하는 API로, 애플리케이션이 운영 체제의 기능을 사용할 수 있도록 합니다. 예를 들어, 파일 시스템 접근, 프로세스 관리 등이 있습니다.

 

예시

 

1. Google Maps API: 개발자가 Google Maps 서비스를 자신의 애플리케이션에 통합할 수 있도록 하는 API입니다. 경로 찾기, 장소 정보 제공, 지도 표시 등의 기능을 제공합니다.

GET https://maps.googleapis.com/maps/api/geocode/json?address=Seattle&key=YOUR_API_KEY

2. Twitter API: Twitter 플랫폼의 데이터를 접근하고 조작할 수 있게 해주는 API입니다. 트윗 작성, 사용자 정보 가져오기, 트윗 검색 등의 기능을 제공합니다.

GET https://api.twitter.com/2/tweets?ids=1453489038376136711&tweet.fields=created_at&expansions=author_id&user.fields=username

API 사용의 장점

 

1. 빠른 개발: 기존의 기능을 재사용함으로써 개발 속도를 높일 수 있습니다.

2. 유지보수 용이: 중앙화된 API를 통해 기능 업데이트와 유지보수가 용이합니다.

3. 호환성: 다양한 시스템 간의 호환성을 보장합니다.

 

참고 자료

 

RESTful API란 무엇인가?

반응형
반응형

 

Firestore를 사용하여 To-Do 앱을 만드는 방법을 설명하겠습니다. 이 예제에서는 Firebase Firestore를 사용하여 할 일 데이터를 저장하고, 읽고, 업데이트하고, 삭제하는 기능을 구현합니다.

 

1. firebase 내 데이터베이스 만들기

위와 같이 firestore 를 생성해줍니다. 

 

1. 프로젝트 설정

 

pubspec.yaml 파일 업데이트

 

최신 Firebase 패키지를 pubspec.yaml 파일에 추가합니다.

dependencies:
  flutter:
    sdk: flutter
  firebase_core: 
  firebase_auth: 
  cloud_firestore:

2. Firebase 설정

 

google-services.json 파일을 다운로드하여 android/app 디렉토리에 복사합니다. Firebase 콘솔에서 프로젝트를 설정하고 앱을 등록한 후 이 파일을 받을 수 있습니다.

 

android/build.gradle 파일 업데이트

buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.0.0'
        classpath 'com.google.gms:google-services:4.4.2'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

android/app/build.gradle 파일 업데이트

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 34

    defaultConfig {
        applicationId "com.example.flutter_todo"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation platform('com.google.firebase:firebase-bom:32.2.2')
}

apply plugin: 'com.google.gms.google-services'

 

3. Firebase 초기화

 

main.dart

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'screens/todo_list_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firebase Todo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}

 

4. Firestore 데이터 모델

 

models/todo.dart

class Todo {
  final String id;
  final String title;
  final bool isDone;

  Todo({
    required this.id,
    required this.title,
    this.isDone = false,
  });

  factory Todo.fromMap(Map<String, dynamic> data, String documentId) {
    return Todo(
      id: documentId,
      title: data['title'] ?? '',
      isDone: data['isDone'] ?? false,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      'title': title,
      'isDone': isDone,
    };
  }
}

5. Firestore 서비스

 

services/firestore_service.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/todo.dart';

class FirestoreService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  Stream<List<Todo>> getTodos() {
    return _db.collection('todos').snapshots().map((snapshot) =>
        snapshot.docs.map((doc) => Todo.fromMap(doc.data(), doc.id)).toList());
  }

  Future<void> addTodo(Todo todo) {
    return _db.collection('todos').add(todo.toMap());
  }

  Future<void> updateTodo(Todo todo) {
    return _db.collection('todos').doc(todo.id).update(todo.toMap());
  }

  Future<void> deleteTodo(String id) {
    return _db.collection('todos').doc(id).delete();
  }
}

6. 프로바이더 설정

 

providers/todo_provider.dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/todo.dart';
import '../services/firestore_service.dart';

class TodoProvider with ChangeNotifier {
  final FirestoreService _firestoreService = FirestoreService();

  late Stream<List<Todo>> _todos;
  Stream<List<Todo>> get todos => _todos;

  TodoProvider() {
    _todos = _firestoreService.getTodos();
  }

  Future<void> addTodo(Todo todo) async {
    await _firestoreService.addTodo(todo);
  }

  Future<void> updateTodo(Todo todo) async {
    await _firestoreService.updateTodo(todo);
  }

  Future<void> deleteTodo(String id) async {
    await _firestoreService.deleteTodo(id);
  }
}

7. UI 작성

 

screens/todo_list_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';
import 'edit_todo_screen.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: StreamBuilder<List<Todo>>(
        stream: Provider.of<TodoProvider>(context).todos,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Center(child: CircularProgressIndicator());
          }
          final todos = snapshot.data!;
          return ListView.builder(
            itemCount: todos.length,
            itemBuilder: (ctx, i) => ListTile(
              title: Text(todos[i].title),
              trailing: Checkbox(
                value: todos[i].isDone,
                onChanged: (value) {
                  Provider.of<TodoProvider>(context, listen: false).updateTodo(
                    Todo(
                      id: todos[i].id,
                      title: todos[i].title,
                      isDone: value!,
                    ),
                  );
                },
              ),
              onLongPress: () {
                Provider.of<TodoProvider>(context, listen: false)
                    .deleteTodo(todos[i].id);
              },
              onTap: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => EditTodoScreen(todo: todos[i]),
                  ),
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (ctx) {
              TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(labelText: 'Title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      if (controller.text.isNotEmpty) {
                        final newTodo = Todo(
                          id: '',
                          title: controller.text,
                        );
                        Provider.of<TodoProvider>(context, listen: false)
                            .addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

screens/edit_todo_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';

class EditTodoScreen extends StatelessWidget {
  final Todo todo;

  EditTodoScreen({required this.todo});

  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController(text: todo.title);

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Todo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (titleController.text.isNotEmpty) {
                  final updatedTodo = Todo(
                    id: todo.id,
                    title: titleController.text,
                    isDone: todo.isDone,
                  );
                  Provider.of<TodoProvider>(context, listen: false)
                      .updateTodo(updatedTodo);
                  Navigator.of(context).pop();
                }
              },
              child: Text('Update'),
            ),
          ],
        ),
      ),
    );
  }
}

요약

 

1. Firestore 데이터 모델(Todo)을 정의합니다.

2. Firestore 서비스(FirestoreService)를 작성하여 Firestore와 상호작용합니다.

3. 프로바이더(TodoProvider)를 작성하여 상태 관리를 합니다.

4. UI를 작성하여 할 일 목록을 표시하고 추가, 수정, 삭제 기능을 구현합니다.

5. Firebase를 초기화하고 앱을 실행합니다.

 

이제 Firestore를 사용하여 할 일 데이터를 관리하는 간단한 To-Do 앱이 완성되었습니다. 필요에 따라 추가 기능을 구현하거나 UI를 개선할 수 있습니다.

반응형
반응형

SQLite를 사용하여 To-Do 데이터를 저장하는 방법을 설명하겠습니다. Flutter 프로젝트에서 SQLite를 사용하기 위해 sqflite 패키지를 사용합니다. 이 패키지를 사용하여 데이터베이스를 생성하고, 할 일 데이터를 저장하고, 읽고, 업데이트하고, 삭제할 수 있습니다.

 

프로젝트 설정

 

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

 

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.0.0+4
  path: ^1.8.0

 

디렉토리 및 파일 구조

lib/
  main.dart
  models/
    todo.dart
  screens/
    todo_list_screen.dart
    edit_todo_screen.dart
  services/
    database_helper.dart
  providers/
    todo_provider.dart

 

1. 모델 정의

 

models/todo.dartㅣㅇve

class Todo {
  String id;
  String title;
  bool isDone;

  Todo({
    required this.id,
    required this.title,
    this.isDone = false,
  });

  // 데이터베이스에서 읽은 데이터를 객체로 변환
  factory Todo.fromMap(Map<String, dynamic> json) => new Todo(
    id: json["id"],
    title: json["title"],
    isDone: json["isDone"] == 1,
  );

  // 객체 데이터를 데이터베이스에 저장할 수 있는 형태로 변환
  Map<String, dynamic> toMap() => {
    "id": id,
    "title": title,
    "isDone": isDone ? 1 : 0,
  };
  
}

2. 데이터베이스 헬퍼 클래스 작성

 

services/database_helper.dart

import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/todo.dart';

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() => _instance;

  static Database? _database;

  DatabaseHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    String path = join(await getDatabasesPath(), 'todo_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE todos(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT,
        isDone INTEGER
      )
    ''');
  }

  Future<List<Todo>> getTodos() async {
    final db = await database;
    var res = await db.query('todos');
    List<Todo> list =
        res.isNotEmpty ? res.map((c) => Todo.fromMap(c)).toList() : [];
    return list;
  }

  Future<int> insertTodo(Todo todo) async {
    final db = await database;
    return await db.insert('todos', todo.toMap());
  }

  Future<int> updateTodo(Todo todo) async {
    final db = await database;
    return await db.update(
      'todos',
      todo.toMap(),
      where: 'id = ?',
      whereArgs: [todo.id],
    );
  }

  Future<int> deleteTodo(int id) async {
    final db = await database;
    return await db.delete(
      'todos',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
}

3. 프로바이더 설정

 

providers/todo_provider.dart

import 'package:flutter/material.dart';
import '../models/todo.dart';
import '../services/database_helper.dart';

class TodoProvider with ChangeNotifier {
  List<Todo> _todos = [];

  List<Todo> get todos => _todos;

  Future<void> loadTodos() async {
    _todos = await DatabaseHelper().getTodos();
    notifyListeners();
  }

  Future<void> addTodo(Todo todo) async {
    await DatabaseHelper().insertTodo(todo);
    await loadTodos();
  }

  Future<void> updateTodo(Todo todo) async {
    await DatabaseHelper().updateTodo(todo);
    await loadTodos();
  }

  Future<void> removeTodo(int id) async {
    await DatabaseHelper().deleteTodo(id);
    await loadTodos();
  }
}

4. UI 작성

 

screens/todo_list_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';
import 'edit_todo_screen.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: FutureBuilder(
        future: Provider.of<TodoProvider>(context, listen: false).loadTodos(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          return Consumer<TodoProvider>(
            builder: (context, todoProvider, child) {
              return ListView.builder(
                itemCount: todoProvider.todos.length,
                itemBuilder: (ctx, i) => ListTile(
                  title: Text(todoProvider.todos[i].title),
                  trailing: Checkbox(
                    value: todoProvider.todos[i].isDone,
                    onChanged: (_) {
                      todoProvider.updateTodo(
                        Todo(
                          id: todoProvider.todos[i].id,
                          title: todoProvider.todos[i].title,
                          isDone: !todoProvider.todos[i].isDone,
                        ),
                      );
                    },
                  ),
                  onLongPress: () {
                    todoProvider.removeTodo(todoProvider.todos[i].id!);
                  },
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) => EditTodoScreen(todo: todoProvider.todos[i]),
                      ),
                    );
                  },
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (ctx) {
              TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(labelText: 'Title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      if (controller.text.isNotEmpty) {
                        final newTodo = Todo(
                          title: controller.text,
                        );
                        Provider.of<TodoProvider>(context, listen: false).addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

screens/edit_todo_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';

class EditTodoScreen extends StatelessWidget {
  final Todo todo;

  EditTodoScreen({required this.todo});

  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController(text: todo.title);

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Todo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (titleController.text.isNotEmpty) {
                  final updatedTodo = Todo(
                    id: todo.id,
                    title: titleController.text,
                    isDone: todo.isDone,
                  );
                  Provider.of<TodoProvider>(context, listen: false).updateTodo(updatedTodo);
                  Navigator.of(context).pop();
                }
              },
              child: Text('Update'),
            ),
          ],
        ),
      ),
    );
  }
}

5. 메인 파일 설정

 

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'screens/todo_list_screen.dart';
import 'providers/todo_provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => TodoProvider()),
      ],
      child: MaterialApp(
        title: 'To-Do App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: TodoListScreen(),
      ),
    );
  }
}

요약

 

1. `sqflite`와 `path` 패키지를 사용하여 SQLite 데이터베이스를 설정합니다.

2. 데이터 모델(`Todo`)을 정의합니다.

3. 데이터베이스 헬퍼 클래스(`DatabaseHelper`)를 작성하여 CRUD 작업을 수행합니다.

4. 프로바이더(`TodoProvider`)를 설정하여 데이터베이스와 UI 간의 상태를 관리합니다.

5. UI를 작성하여 할 일 목록을 표시하고, 할 일을 추가, 수정, 삭제할 수 있는 기능을 제공합니다.

6. 메인 파일에서 MultiProvider를 설정하여 앱을 실행합니다.

 

 

반응형
반응형

Flutter에서 바텀 네비게이션 바(Bottom Navigation Bar)는 주로 앱의 주요 네비게이션을 제공하는 데 사용됩니다. 사용자에게 앱의 주요 섹션을 탐색할 수 있는 인터페이스를 제공합니다. 아래는 바텀 네비게이션 바에 대한 주요 특징과 예제들을 정리한 것입니다.

 

주요 특징

 

1. 사용 용도:

앱의 주요 섹션 간 빠른 네비게이션을 제공.

일반적으로 3~5개의 탭을 포함.

각 탭에는 아이콘과 선택적으로 텍스트 레이블을 포함할 수 있음.

2. 구성 요소:

items: 네비게이션 항목 목록으로, 각각 BottomNavigationBarItem 객체를 포함.

currentIndex: 현재 선택된 탭의 인덱스.

onTap: 사용자가 탭을 선택할 때 호출되는 콜백 함수.

type: 네비게이션 바의 타입(fixed 또는 shifting).

 

예제 코드

 

아래는 Flutter에서 바텀 네비게이션 바를 구현하는 기본적인 예제입니다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    Text('Home Page'),
    Text('Search Page'),
    Text('Profile Page'),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Example'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

주요 속성

 

1. items:

BottomNavigationBarItem 객체의 리스트로, 각 항목은 아이콘과 텍스트 레이블을 가짐.

예제:

items: const <BottomNavigationBarItem>[
  BottomNavigationBarItem(
    icon: Icon(Icons.home),
    label: 'Home',
  ),
  BottomNavigationBarItem(
    icon: Icon(Icons.search),
    label: 'Search',
  ),
  BottomNavigationBarItem(
    icon: Icon(Icons.person),
    label: 'Profile',
  ),
],

 

2. currentIndex:

현재 선택된 탭의 인덱스를 나타냄.

예제:

currentIndex: _selectedIndex,

 

3. onTap:

사용자가 탭을 선택할 때 호출되는 콜백 함수.

예제:

onTap: _onItemTapped,

4. type:

네비게이션 바의 타입을 설정. BottomNavigationBarType.fixed 또는 BottomNavigationBarType.shifting을 사용.

예제:

type: BottomNavigationBarType.fixed,

참고 자료

 

Flutter Documentation - BottomNavigationBar

Flutter Cookbook - BottomNavigationBar

반응형
반응형

Flutter에서는 다양한 버튼 위젯을 제공하여 사용자가 다양한 상호작용을 할 수 있도록 지원합니다. 각 버튼 위젯은 고유한 스타일과 기능을 가지고 있어 다양한 사용 사례에 적합합니다. 주요 버튼 위젯과 그 특징을 정리해보겠습니다.

 

주요 버튼 위젯

 

1. ElevatedButton

설명: 이전의 RaisedButton에 해당하며, 입체감을 제공하는 기본 버튼입니다.

특징:

사용자가 버튼을 누를 때 시각적인 효과를 제공

스타일을 통해 색상, 모양 등을 커스터마이징 가능

예제:

ElevatedButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Elevated Button'),
);

2. TextButton

설명: 이전의 FlatButton에 해당하며, 배경 없이 텍스트로만 구성된 버튼입니다.

특징:

버튼을 누를 때 배경 색상이 변경됨

간단한 텍스트 버튼을 구현할 때 사용

예제:

TextButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Text Button'),
);

3. OutlinedButton

설명: 외곽선이 있는 버튼으로, 이전의 OutlineButton에 해당합니다.

특징:

버튼을 누를 때 외곽선 색상이 변경됨

기본 버튼에 외곽선을 추가하여 강조할 때 사용

예제:

OutlinedButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Outlined Button'),
);

4. IconButton

설명: 아이콘을 포함하는 버튼으로, 아이콘만으로 구성됩니다.

특징:

아이콘을 클릭할 때 사용

버튼에 텍스트 대신 아이콘을 사용하여 직관적인 인터페이스 제공

예제:

IconButton(
  icon: Icon(Icons.thumb_up),
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
);

5. FloatingActionButton

설명: 둥근 모양의 떠 있는 버튼으로, 일반적으로 주요 동작을 나타냅니다.

특징:

화면 위에 떠 있는 듯한 시각적 효과

주로 주요 액션 버튼으로 사용

예제:

FloatingActionButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Icon(Icons.add),
);

버튼 스타일링

 

Flutter의 버튼 위젯들은 스타일링 옵션을 제공하여 다양한 커스터마이징이 가능합니다. 예를 들어, ElevatedButton의 스타일을 설정하는 방법은 다음과 같습니다:

ElevatedButton(
  onPressed: () {},
  child: Text('Styled Button'),
  style: ElevatedButton.styleFrom(
    primary: Colors.blue, // 배경 색상
    onPrimary: Colors.white, // 텍스트 색상
    padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
    textStyle: TextStyle(fontSize: 20),
  ),
);
반응형
반응형

Flutter에서는 다양한 입력 위젯을 제공하여 사용자의 입력을 처리할 수 있습니다.

주요 입력 위젯과 그 특징을 정리해보겠습니다.

 

주요 입력 위젯

 

1. TextField

설명: 텍스트 입력 필드로, 사용자가 텍스트를 입력할 수 있도록 합니다.

특징:

기본 텍스트 입력

다수의 속성을 통해 입력 형식, 스타일 등을 제어 가능

controller를 통해 입력된 텍스트를 관리

예제:

TextField(
  decoration: InputDecoration(
    border: OutlineInputBorder(),
    labelText: 'Enter your name',
  ),
);

 

2. TextFormField

설명: Form과 함께 사용되는 텍스트 입력 필드로, 폼 유효성 검사를 지원합니다.

특징:

TextField와 유사하지만 폼 유효성 검사 기능이 추가됨

validator를 통해 유효성 검사 로직 구현 가능

예제:

TextFormField(
  decoration: InputDecoration(
    border: OutlineInputBorder(),
    labelText: 'Enter your email',
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
  },
);

3. Checkbox

설명: 체크박스 위젯으로, 사용자가 옵션을 선택하거나 해제할 수 있습니다.

특징:

valueonChanged를 통해 상태 관리

예제:

Checkbox(
  value: true,
  onChanged: (bool? newValue) {
    // Handle change
  },
);

4. Radio

설명: 라디오 버튼 위젯으로, 그룹 내에서 하나의 옵션을 선택할 수 있습니다.

특징:

여러 라디오 버튼이 같은 그룹에 속할 수 있음

valuegroupValue, onChanged를 통해 상태 관리

예제:

Radio<int>(
  value: 1,
  groupValue: selectedValue,
  onChanged: (int? newValue) {
    // Handle change
  },
);

5. Switch

설명: 스위치 위젯으로, 이진 상태를 나타내는 토글 버튼입니다.

특징:

valueonChanged를 통해 상태 관리

예제:

Switch(
  value: true,
  onChanged: (bool newValue) {
    // Handle change
  },
);

6. Slider

설명: 슬라이더 위젯으로, 사용자가 값을 선택할 수 있도록 합니다.

특징:

최소 및 최대 값 설정 가능

valueonChanged를 통해 상태 관리

예제:

Slider(
  value: 50,
  min: 0,
  max: 100,
  onChanged: (double newValue) {
    // Handle change
  },
);

7. DropdownButton

설명: 드롭다운 버튼 위젯으로, 사용자가 목록에서 하나의 옵션을 선택할 수 있습니다.

특징:

itemsonChanged를 통해 상태 관리

예제:

DropdownButton<String>(
  value: selectedValue,
  items: <String>['One', 'Two', 'Three'].map<DropdownMenuItem<String>>((String value) {
    return DropdownMenuItem<String>(
      value: value,
      child: Text(value),
    );
  }).toList(),
  onChanged: (String? newValue) {
    // Handle change
  },
);

참고 자료

 

Flutter Documentation - TextField

Flutter Documentation - TextFormField

Flutter Documentation - Checkbox

Flutter Documentation - Radio

Flutter Documentation - Switch

Flutter Documentation - Slider

Flutter Documentation - DropdownButton

반응형

+ Recent posts