반응형

이 앱은 “할 일 목록 (To-Do List)” 앱으로, 기본적인 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 포함할 것입니다.

 

샘플 앱: To-Do List

 

1. 프로젝트 생성

 

Flutter 프로젝트를 생성합니다.

flutter create todo_app
cd todo_app

 

2. 주요 패키지 추가

 

pubspec.yaml 파일에 필요한 패키지를 추가합니다. 이 예제에서는 provider를 상태 관리를 위해 사용합니다.

dev_dependencies:
  flutter_test:
    sdk: flutter
  provider:
  uuid:

이후 flutter pub get 명령어를 실행해 패키지를 설치합니다.

 

3. 모델 정의

 

할 일 항목의 모델 클래스를 정의합니다.

// lib/models/todo.dart
class Todo {
  String id;
  String title;
  bool isDone;

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

4. 상태 관리 설정

 

할 일 목록을 관리하는 ChangeNotifier 클래스를 정의합니다.

import 'package:flutter/foundation.dart';
import '../model/todo.dart';

// 할 일 목록을 관리하는 ChangeNotifier 클래스
class TodoProvider with ChangeNotifier {
  List<Todo> _todos = [];

  List<Todo> get todos => _todos;

  // 할 일 항목을 추가하는 메서드
  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners();
  }

  // 할 일 항목의 완료 상태를 토글하는 메서드
  void toggleTodoStatus(String id) {
    final index = _todos.indexWhere((todo) => todo.id == id);
    if (index != -1) {
      _todos[index].isDone = !_todos[index].isDone;
      notifyListeners();
    }
  }

  // 할 일 항목을 제거하는 메서드
  void removeTodo(String id) {
    _todos.removeWhere((todo) => todo.id == id);
    notifyListeners();
  }
// 업데이트
  void updateTodo(Todo updatedTodo) {
    final index = _todos.indexWhere((todo) => todo.id == updatedTodo.id);
    if (index != -1) {
      _todos[index] = updatedTodo;
      notifyListeners();
    }
  }
}

참고 if 문에 -1 확인 이유

f 문을 사용하여 index-1이 아닌지 확인하는 이유는 indexWhere 메서드가 일치하는 항목을 찾지 못할 때 -1을 반환하기 때문입니다. index-1이면 _todos[index]는 유효하지 않으며, 이 경우 배열에 접근하려고 하면 오류가 발생합니다. 따라서 if 문은 안전한 코드를 작성하는 데 중요합니다.

 

그럼에도 불구하고, indexWhere가 항상 올바른 값을 반환한다고 확신할 수 있다면 이 조건문을 생략할 수 있지만, 이는 코드의 안정성과 유연성을 감소시킬 수 있습니다. 일반적으로는 if 문을 포함하는 것이 좋습니다.

 

5. 메인 파일 설정

 

프로바이더를 설정하고 메인 파일을 구성합니다.

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

void main() {
  runApp(
    MultiProvider(
      providers: [
        // TodoProvider를 ChangeNotifierProvider로 제공
        ChangeNotifierProvider(create: (_) => TodoProvider()),
      ],
      child: MyApp(),
    ),
  );
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'To-Do App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}

6. UI 작성

 

할 일 목록 화면을 작성합니다.

// lib/screens/todo_list_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_todo/screen/todo_edit_screen.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';

import '../model/todo.dart';
import '../providers/todo_provider.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final todoProvider = Provider.of<TodoProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: 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.toggleTodoStatus(todoProvider.todos[i].id); // 완료 상태 토글
            },
          ),
          onTap: () {
            // 수정 페이지로 이동
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => EditTodoScreen(todo: todoProvider.todos[i]),
              ),
            );
          },
          onLongPress: () {
            todoProvider.removeTodo(todoProvider.todos[i].id); // 할 일 항목 제거
          },
        ),
      ),
      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: Uuid().v4(),
                          title: controller.text,
                        );
                        todoProvider.addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

7. 수정 페이지 생성

 

새로운 파일 edit_todo_screen.dart를 생성합니다.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/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'),
            ),
          ],
        ),
      ),
    );
  }
}

 

 

반응형
반응형

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
반응형

BottomNavigationBar는 Flutter에서 화면 하단에 배치하여 여러 화면으로 쉽게 전환할 수 있도록 도와주는 네비게이션 바입니다.

일반적으로 3~5개의 탭을 가지며, 각 탭을 선택하면 해당 화면으로 이동할 수 있습니다.

 

주요 속성

 

items: 네비게이션 바에 표시될 항목 목록 (BottomNavigationBarItem).

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

onTap: 항목이 탭될 때 호출되는 콜백 함수.

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

backgroundColor: 네비게이션 바의 배경색.

selectedItemColor: 선택된 항목의 색상.

unselectedItemColor: 선택되지 않은 항목의 색상.

 

기본 사용 예제

 

아래는 BottomNavigationBar를 사용하여 화면 하단에 네비게이션 바를 배치하고, setState를 사용하여 화면을 업데이트하는 예제입니다.

 

코드 예제 

import 'package:flutter/material.dart';

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

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

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  
  late TabController _tabController;
  int _selectedIndex = 0;
  
  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _tabController.addListener(
      () => setState(() => _selectedIndex = _tabController.index)
    );
  }

  // 페이지 종료 시 종료해 주는 기능
  @override
  void dispose() {
    super.dispose();

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("test title"),
        ),
        body: _selectedIndex == 0
      ? tabContainer(context, Colors.indigo, "Friends Tab")
        : _selectedIndex == 1 ? tabContainer(context, Colors.yellow, "Chats Tab")
        : tabContainer(context, Colors.blue, "Setting Tab"),
      bottomNavigationBar: SizedBox(
        height: 90,
        child: TabBar(
          controller: _tabController,
          labelColor: Colors.black,
          tabs: [
            Tab(
             icon: Icon(
                 _selectedIndex == 0 ? Icons.person : Icons.person_2_outlined
             ),
             text: "Friends",
            ),
            Tab(
              icon: Icon(
                  _selectedIndex == 1 ? Icons.chat : Icons.chat_outlined
              ),
              text: "Chats",
            ),
            Tab(
              icon: Icon(
                  _selectedIndex == 2 ? Icons.settings : Icons.settings_outlined
              ),
              text: "Settings",
            ),
          ],
        ),
      ),
    );
  }

  Widget tabContainer(BuildContext con, Color tabColor, String tabText) {
    return Container(
      width: MediaQuery.of(con).size.width,
      height: MediaQuery.of(con).size.height,
      color: tabColor,
      child: Center(
        child: Text(
          tabText,
          style: const TextStyle(
            color: Colors.white
          ),
        ),
      ),
    );
  }

}

 

 

다른 예제 

import 'package:flutter/material.dart';

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

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

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Home Page',
      style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
    ),
    Text(
      'Search Page',
      style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
    ),
    Text(
      'Profile Page',
      style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
    ),
  ];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar 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. BottomNavigationBarItem: 네비게이션 바에 표시될 항목을 정의합니다. iconlabel 속성을 가집니다.

2. currentIndex: 현재 선택된 항목의 인덱스를 저장합니다.

3. onTap: 항목이 탭될 때 호출되는 콜백 함수로, 선택된 인덱스를 업데이트하고 화면을 새로 고칩니다.

4. setState: 상태 변화를 적용하여 UI를 다시 빌드합니다.

 

BottomNavigationBar와 setState 설명

 

BottomNavigationBar

 

BottomNavigationBar는 하단 네비게이션 바를 만드는 데 사용됩니다. 여러 개의 탭을 제공하여 각 탭에 다른 화면을 연결할 수 있습니다.

 

items: BottomNavigationBarItem 목록으로, 각 항목에는 iconlabel이 포함됩니다.

currentIndex: 현재 선택된 탭의 인덱스를 지정합니다.

onTap: 탭이 선택될 때 호출되는 콜백 함수로, 탭의 인덱스를 매개변수로 받습니다.

 

setState

 

setState는 상태가 변경될 때 UI를 다시 빌드하는 데 사용됩니다. 네비게이션 바의 선택된 인덱스를 업데이트할 때 호출됩니다.

 

setState(): 내부 상태를 업데이트하고 변경된 상태를 반영하기 위해 UI를 다시 빌드합니다. 상태 변경이 필요할 때마다 호출해야 합니다.

 

요약

 

BottomNavigationBar는 Flutter에서 화면 하단에 네비게이션 바를 배치하여 여러 화면으로 쉽게 전환할 수 있도록 도와줍니다.

setState는 상태 변경 시 UI를 다시 빌드하는 데 사용됩니다.

BottomNavigationBarsetState를 함께 사용하여 탭 선택 시 화면을 업데이트할 수 있습니다.

반응형
반응형

sharedPreferences는 Flutter에서 간단한 키-값 쌍을 영구적으로 저장하는 데 사용되는 패키지입니다.

이 패키지는 Android의 SharedPreferences와 iOS의 NSUserDefaults를 래핑합니다.

작은 데이터를 영구적으로 저장하고자 할 때 유용합니다.

 

주요 기능

 

데이터 저장: 문자열, 정수, 부울, 더블 등의 간단한 데이터를 저장할 수 있습니다.

데이터 읽기: 저장된 데이터를 읽어올 수 있습니다.

데이터 삭제: 특정 키에 해당하는 데이터를 삭제할 수 있습니다.

모든 데이터 삭제: 모든 데이터를 삭제할 수 있습니다.

 

1. 패키지 추가

 

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

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.6

 

2. 패키지 가져오기

 

Dart 파일에서 shared_preferences 패키지를 가져옵니다.

import 'package:shared_preferences/shared_preferences.dart';

 

3. 데이터 저장

 

데이터를 저장하는 예제입니다.

Future<void> saveData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('username', 'John Doe');
  await prefs.setInt('age', 30);
  await prefs.setBool('isLoggedIn', true);
  await prefs.setDouble('height', 1.75);
}

4. 데이터 읽기

 

저장된 데이터를 읽어오는 예제입니다.

Future<void> readData() async {
  final prefs = await SharedPreferences.getInstance();
  String? username = prefs.getString('username');
  int? age = prefs.getInt('age');
  bool? isLoggedIn = prefs.getBool('isLoggedIn');
  double? height = prefs.getDouble('height');

  print('Username: $username');
  print('Age: $age');
  print('Is Logged In: $isLoggedIn');
  print('Height: $height');
}

 

5. 데이터 삭제

 

특정 키에 해당하는 데이터를 삭제하는 예제입니다.

Future<void> removeData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('username');
}

6. 모든 데이터 삭제

 

모든 데이터를 삭제하는 예제입니다.

Future<void> clearData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.clear();
}



예시 

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

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

class _MyHomePageState extends State<MyHomePage> {

  late SharedPreferences _prefs;
  String _username = "";
  final TextEditingController _userController = TextEditingController();

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

  _saveUsername() {
    setState(() {
      _username = _userController.text;
      _prefs.setString("currentUsername", _username);
    });
  }

  _getUsername() async {
    _prefs = await SharedPreferences.getInstance();
    setState(() {
      _username = _prefs.getString("currentUsername") ?? "";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("test title"),
        ),
        body: Container(
          child: Column(
            children: [
              Text("현재 사용자 이름 : $_username" ),
              Container(
                child: TextField(
                  controller: _userController,
                  textAlign: TextAlign.left,
                  decoration: const InputDecoration(
                    border: InputBorder.none,
                    hintText: 'Input your username'
                  ),
                ),
              ),
              TextButton(
                  onPressed: () => _saveUsername()
                  , child: Text('저장')
              )
            ],
          ),
        )

    );
  }
}



다른 예제 앱

 

간단한 예제 앱을 만들어 보겠습니다.

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

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

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

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late SharedPreferences prefs;
  String username = '';
  int age = 0;
  bool isLoggedIn = false;
  double height = 0.0;

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

  Future<void> _loadData() async {
    prefs = await SharedPreferences.getInstance();
    setState(() {
      username = prefs.getString('username') ?? '';
      age = prefs.getInt('age') ?? 0;
      isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
      height = prefs.getDouble('height') ?? 0.0;
    });
  }

  Future<void> _saveData() async {
    await prefs.setString('username', 'John Doe');
    await prefs.setInt('age', 30);
    await prefs.setBool('isLoggedIn', true);
    await prefs.setDouble('height', 1.75);
    _loadData();
  }

  Future<void> _removeData() async {
    await prefs.remove('username');
    _loadData();
  }

  Future<void> _clearData() async {
    await prefs.clear();
    _loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SharedPreferences Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text('Username: $username'),
            Text('Age: $age'),
            Text('Is Logged In: $isLoggedIn'),
            Text('Height: $height'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _saveData,
              child: Text('Save Data'),
            ),
            ElevatedButton(
              onPressed: _removeData,
              child: Text('Remove Username'),
            ),
            ElevatedButton(
              onPressed: _clearData,
              child: Text('Clear All Data'),
            ),
          ],
        ),
      ),
    );
  }
}

 

주요 포인트

 

SharedPreferences: 간단한 키-값 쌍을 영구적으로 저장하는 데 사용됩니다.

getInstance(): SharedPreferences의 인스턴스를 얻기 위해 사용됩니다.

setString, setInt, setBool, setDouble: 데이터를 저장하는 메서드입니다.

getString, getInt, getBool, getDouble: 데이터를 읽어오는 메서드입니다.

remove: 특정 키에 해당하는 데이터를 삭제하는 메서드입니다.

clear: 모든 데이터를 삭제하는 메서드입니다.

 

SharedPreferences 문서

 

SharedPreferences 패키지 문서

 

 

반응형
반응형

 

1. JSON 파일 추가 및 등록: JSON 파일을 assets 폴더에 추가하고 pubspec.yaml 파일에 등록합니다.

 

{
  "users": [
    {
      "id": 1,
      "username": "Anna",
      "email": "anna@gmail.com"
    },
    {
      "id": 2,
      "username": "David",
      "email": "david@gmail.com"
    },
    {
      "id": 3,
      "username": "brian",
      "email": "brian@gmail.com"
    },
    {
      "id": 4,
      "username": "roky",
      "email": "roky@gmail.com"
    },
    {
      "id": 5,
      "username": "sam",
      "email": "sam@gmail.com"
    },
    {
      "id": 6,
      "username": "dead",
      "email": "dead@gmail.com"
    }
  ]
}

 

2. JSON 파일 읽기 및 파싱

 

dart:convertflutter/services.dart 패키지를 사용하여 JSON 파일을 읽고 파싱합니다.

 

static Future loadJson() async {
    final String response = await rootBundle.loadString("lib/users.json");
    final data = await json.decode(response);
    return data['users'];
  }

  Future userList = loadJson();

 

3. 위젯에 데이터 노출하기

Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("test title"),
        ),
        body: Container(
          child: FutureBuilder(
            future: userList,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return ListView.builder(
                  itemCount: snapshot.data.length,
                    itemBuilder: (context, index) {
                      return Container(
                        padding: const EdgeInsets.all(10),
                        child: Text(
                            "${snapshot.data[index]["id"]}: ${snapshot.data[index]["username"]}"
                        ),
                      );
                    }
                );
              } else if (snapshot.hasError) {
                return const Center(
                  child: Text("Error"),
                );
              } else {
                return const Center(
                    child: CircularProgressIndicator(
                        strokeWidth: 2,
                    )
                );
              }
            },
          ),
        )
    )

결과물

 

반응형
반응형

 

Flutter 앱에서 YouTube 동영상을 가져와 표시하기 위해 youtube_player_flutter 패키지를 사용할 수 있습니다. 이 패키지는 YouTube 동영상을 임베드할 수 있는 위젯을 제공합니다. 아래는 Flutter 프로젝트에서 YouTube 동영상을 가져와 표시하는 방법을 단계별로 설명합니다.

단계별 가이드

 

1. 패키지 설치

 

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

dependencies:
  flutter:
    sdk: flutter
  youtube_player_flutter: ^8.0.0

 

2. 패키지 가져오기

 

Dart 파일에서 youtube_player_flutter 패키지를 가져옵니다.

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

 

3. YouTube Player Controller 설정

 

YouTube 동영상을 제어할 수 있는 컨트롤러를 설정합니다. 컨트롤러를 사용하여 동영상을 로드하고 재생합니다.

유튜브 삽입 코드

static String youtubeId = 't5Vj0jeYeEE';

final YoutubePlayerController _con = YoutubePlayerController(
  initialVideoId: youtubeId,
  flags: const YoutubePlayerFlags(
      autoPlay: false
  )
);

@override
Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(
        title: const Text("test title"),
      ),
      body: Center(
        child: YoutubePlayer(
          controller: _con,
          showVideoProgressIndicator: true,
          onReady: () {
            print('Player is ready.');
          },
        ),
      )
  );
}

 

반응형
반응형

Navigator 위젯은 Flutter에서 화면 간의 네비게이션을 관리하는 데 사용됩니다.

Flutter 앱은 일반적으로 여러 화면(Screen)으로 구성되며, 사용자가 다양한 화면 간을 이동할 수 있도록 Navigator를 사용합니다. Navigator는 스택(Stack) 자료구조를 사용하여 화면을 관리합니다.

새로운 화면을 푸시(Push)하고 이전 화면을 팝(Pop)하는 방식으로 작동합니다.

 

주요 메서드

 

1. push: 새로운 화면을 스택에 추가합니다.

2. pop: 현재 화면을 스택에서 제거합니다.

3. pushReplacement: 현재 화면을 새 화면으로 교체합니다.

4. pushAndRemoveUntil: 특정 조건에 따라 스택의 모든 화면을 제거하고 새로운 화면을 추가합니다.

5. popUntil: 특정 조건에 따라 스택의 화면을 제거합니다.

 

 

현재 페이지에서 새로만든 SecondView 라는 페이지로 이동을 원할 경우

body: Center(
  child: GestureDetector(
    onTap: () => Navigator.push(context, MaterialPageRoute(
        builder: (_) => SecondView())
    ),
    child: Container(
      padding: EdgeInsets.all(15),
      color: Colors.blue,
      child: Text("Get Started"),
    ),
  ),
)

이동하는 페이지에서는 다음과 같이 구현할 수 있습니다. 

뒤로가기 아이콘의 onPressed 에 pop() 이라는 메소드로 현재 위로 올라온 페이지를 제거하여 뒤로 가는 방법입니다. 

import 'package:flutter/material.dart';

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

  @override
  State<SecondView> createState() => _SecondViewState();
}

class _SecondViewState extends State<SecondView> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.arrow_back),
            onPressed: () => Navigator.of(context).pop(),
          ),
          title: const Text("test title"),
        ),
        body: Center(
          child: Container(
            padding: EdgeInsets.all(15),
            color: Colors.blue,
            child: Text("This is the second view"),
          ),
        )
    );
  }
}

 

 

 

다른 예시

import 'package:flutter/material.dart';

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

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

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(builder: (context) => FirstPage()),
            );
          },
          child: Text('Go Back to First Page'),
        ),
      ),
    );
  }
}

 

3. pushAndRemoveUntil

import 'package:flutter/material.dart';

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

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

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushAndRemoveUntil(
              context,
              MaterialPageRoute(builder: (context) => ThirdPage()),
              (Route<dynamic> route) => false,
            );
          },
          child: Text('Go to Third Page and remove all previous pages'),
        ),
      ),
    );
  }
}

class ThirdPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Third Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go Back'),
        ),
      ),
    );
  }
}

 

 

 

주요 포인트

 

push: 새로운 화면을 스택에 추가합니다.

pop: 현재 화면을 스택에서 제거합니다.

pushReplacement: 현재 화면을 새 화면으로 교체하고, 이전 화면을 스택에서 제거합니다.

pushAndRemoveUntil: 특정 조건에 따라 스택의 모든 화면을 제거하고 새로운 화면을 추가합니다.

popUntil: 특정 조건에 따라 스택의 화면을 제거합니다.

 

추가 메서드

 

canPop: 현재 화면이 스택에서 팝될 수 있는지 확인합니다.

maybePop: 팝될 수 있는 경우 화면을 팝합니다.

popAndPushNamed: 현재 화면을 팝하고 새로운 화면을 이름으로 푸시합니다.

pushNamed: 이름으로 새로운 화면을 푸시합니다.

 

Navigator 문서

 

Navigator 클래스 문서

MaterialPageRoute 클래스 문서

반응형
반응형

 

Flutter에서 버튼은 다양한 스타일과 기능을 제공하는 UI 요소로, 사용자와의 상호작용을 위해 사용됩니다.

주요 버튼 위젯으로는 ElevatedButton, TextButton, OutlinedButton, 그리고 IconButton 등이 있습니다.

각 버튼 위젯은 특정 상황에 맞게 사용될 수 있습니다.

 

주요 버튼 위젯

 

1. ElevatedButton

2. TextButton

3. OutlinedButton

4. IconButton

5. FloatingActionButton

 

1. ElevatedButton

 

ElevatedButton은 입체적인 디자인을 가진 버튼으로, 주로 액션을 강조할 때 사용됩니다.

 

주요 속성

 

onPressed: 버튼이 클릭되었을 때 실행될 콜백 함수.

child: 버튼 내에 표시될 위젯 (일반적으로 텍스트).

style: 버튼의 스타일을 정의합니다.

style: ElevatedButton.styleFrom(
          primary: Colors.blue,
          onPrimary: Colors.white,
          padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
        ),

 

2. TextButton

 

TextButton은 텍스트만 있는 버튼으로, 보통 중요하지 않은 액션이나 링크를 나타낼 때 사용됩니다.

 

주요 속성

 

onPressed: 버튼이 클릭되었을 때 실행될 콜백 함수.

child: 버튼 내에 표시될 위젯 (일반적으로 텍스트).

style: 버튼의 스타일을 정의합니다.

style: TextButton.styleFrom(
          primary: Colors.blue,
          padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
        ),

 

3. OutlinedButton

 

OutlinedButton은 테두리가 있는 버튼으로, 보통 중요도가 낮은 액션이나 보조적인 작업을 나타낼 때 사용됩니다.

 

주요 속성

 

onPressed: 버튼이 클릭되었을 때 실행될 콜백 함수.

child: 버튼 내에 표시될 위젯 (일반적으로 텍스트).

style: 버튼의 스타일을 정의합니다.

style: OutlinedButton.styleFrom(
          primary: Colors.blue,
          side: BorderSide(color: Colors.blue),
          padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
        ),

 

 

 

body: Container(
  child: Column(
    children: [
      Center(
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.yellow
          ),
          onPressed: () => print("ElevatedButton"),
          child: const Text("ElevatedButton"),
        ),
      ),
      Center(
        child: TextButton(
          onPressed: () => print("TextButton"),
          child: const Text("TextButton"),
        ),
      ),
      Center(
        child: OutlinedButton(
          onPressed: () => print("OutlinedButton"),
          child: const Text("OutlinedButton"),
        ),
      )
    ],
  ),
)

 

4. IconButton

 

IconButton은 아이콘을 표시하는 버튼으로, 주로 툴바나 내비게이션 바에 사용됩니다.

 

주요 속성

 

icon: 버튼 내에 표시될 아이콘.

onPressed: 버튼이 클릭되었을 때 실행될 콜백 함수.

tooltip: 버튼에 마우스를 올렸을 때 표시될 툴팁.

IconButton(
      icon: Icon(Icons.search),
      onPressed: () {
        print('Search button clicked');
      },
      tooltip: 'Search',
    ),

 

 

5. FloatingActionButton

 

FloatingActionButton은 떠 있는 액션 버튼으로, 화면의 주요 액션을 나타내는 데 사용됩니다.

 

주요 속성

 

onPressed: 버튼이 클릭되었을 때 실행될 콜백 함수.

child: 버튼 내에 표시될 위젯 (일반적으로 아이콘).

floatingActionButton: FloatingActionButton(
          onPressed: () {
            print('FloatingActionButton clicked');
          },
          child: Icon(Icons.add),
          backgroundColor: Colors.blue,
        ),

 

 

 

주요 포인트

 

ElevatedButton: 입체적이고 주요 액션을 강조하는 버튼입니다.

TextButton: 텍스트만 있는 버튼으로, 중요하지 않은 액션이나 링크를 나타낼 때 사용합니다.

OutlinedButton: 테두리가 있는 버튼으로, 보조적인 작업을 나타낼 때 사용됩니다.

IconButton: 아이콘을 표시하는 버튼으로, 툴바나 내비게이션 바에 사용됩니다.

FloatingActionButton: 주요 액션을 나타내는 떠 있는 버튼입니다.

반응형

+ Recent posts