Flutter Path provider는 이
미지를 저장해야 할 경로에 쉽게 접근할 수 있는 패키지이다.
또한 운영 체제에 의해 삭제되지 않는다.
이 패키지는 우리 응용 프로그램의 데이터를 저장하는 데 사용할 수 있는 경로에 쉽게 접근할 수 있게 해준다.
또한 path라는 패키도 설치해주어야한다. 언듯보면 비슷해보이지만
파일 경로 작업 프로세스를 단순화한 패키지이다.
가끔 수동으로 경로를 잡아야할 때가 있다. 파일을 만들던가 말이다. 이때 이 패키지는 경로를 쉽게 구성해 다양한 운영 체제에서 작동하게 해준 미친 패키지이다.
마지막으로는 sqflite 패키지이다.
이 패키지는 SQL 명령을 이용해 장치에 데이터를 저장하게 해준다.
기본적으로 장치에 기본 SQL 데이터베이스를 생성하고 데이터베이스 내의 장소의 제목이나 좌표 같은 데이터를 저장하도록 도와준다.
참고로 여기서 DB란 앱이 실행되는 장치에서 로컬로 실행되는 데이터베이스이다.
share preference 참고로 이건 또다른 패키지이다.
sqflite 처럼 장치에 데이터를 저장할 수 있도록 해준다.
차이점은 sqflite 패키지보다 좀 더 simple하다.
또 같은 기호를 가지고 단순한 키 값 쌍을 저장할 수 있다.
우선 먼저 데이터를 저장할 수 있는, 또 운영 체제에 의해 삭제되지 않는 path를 설정해보자.
운영체제가 우리의 app에 할당하는 로컬 스토리지의 위치가 다르다.
하지만 우리는 하나의 code block을 가지고 여러 운영체제를 build 해야하기 때문에 따라서 path provider가 필요했던 것이었다.
패키지를 이용하면 앱이 실행 중인 운영체제를 위한 경로를 쉽게 설정할 수 있기 때문이다.
import 'package:path_provider/path_provider.dart' as syspaths;
syspaths
라는 명칭으로 alias를 설정해주는 것이 좋다. 왜냐하면 앞서 설치한 path 패키지와 이름이 출동할 가능성이 있기 때문이다.final appDir = syspaths.getApplicationDocumentsDirectory();
참고로 해당 메서드는 Future 객체로 Promise처럼 처리를 해주어야한다.
저장된 image의 최신 path를 가져오려면 image.copy 메서드를 사용하여 최신 경로를 가져와야한다.
또한 copy 메서드를 사용해야하는 이유는 경로중 dispose 되었거나 handler가 dispose 된 경우 에러가 나기 때문이다. 참고로 copy 메서드 또한 Futrue 객체이다.
물론 copy 메서드에 들어갈 경로는 image 본인의 이름도 포함되어야한다.
void addPlace(String title, File image, PlaceLocation location) async {
final appDir = await syspaths.getApplicationDocumentsDirectory();
final fliename = path.basename(image.path);
import 'package:sqflite/sqflite.dart' as sql;
import 'package:sqflite/sqlite_api.dart';
f
lite이고 두번째는 그냥 sqlite이다. f의 유무의 차이가 있으니 주의하자.첫째 인자로는 경로를 받으며 여러 설정들을 할 수 있다. 그 중 onCreated 속성값은 해당 path에 db가 존재하지 않을 경우 db를 생성할 수 있는 함수를 설정해줄 수 있다.
그리고 위 사진에서 알 수 있듯 반드시 Future 객체로 반환해주어야한다.
그래서 Future 객체에 await 키워드를 걸어준 후 해당 결과값을 변수(db
)에 담아준다.
해당 변수에 접근 하거나 create 되었다면 해당 db에 접근하여 이제 비로소 query문을 사용할 수 있다.
그리고 이론적으로 쿼리를 변경할 때마다 version 증가해야한다.
데이터베이스 구조를 변경하면 새로운 데이터베이스 파일이 생성되기 때문이다.
자, 이렇게 insert를 했으면 당연 select를 해와야한다.
이는 app이 실행되었을 때 select 해오면 좋을 듯 하다.
우선 코드의 중복을 막기위해 중복되는 코드(db에 연결)를 따로 빼주었다.
Future<Database> _getDatabase() async {
final dbPath = await sql.getDatabasesPath();
final db = await sql.openDatabase(path.join(dbPath, 'places.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE user_places(id TEXT PRIMARY KEY, title TEXT, image TEXT, lat REAL, lng REAL, address TEXT)');
}, version: 1);
return db;
}
class UserPlacesNotifier extends StateNotifier<List<Place>> {
UserPlacesNotifier() : super(const []);
void loadPlaces() async {
final db = await _getDatabase();
final data = await db.query('user_places');
final places = data
.map(
(row) => Place(
id: row['id'] as String,
title: row['title'] as String,
image: File(row['image'] as String),
location: PlaceLocation(
latitude: row['latitude'] as double,
longtitude: row['longtitude'] as double,
address: row['address'] as String),
),
)
.toList();
state = places;
}
....
import 'dart:io';
import 'package:uuid/uuid.dart';
const uuid = Uuid();
class PlaceLocation {
const PlaceLocation(
{required this.latitude,
required this.longtitude,
required this.address});
final double latitude;
final double longtitude;
final String address;
}
class Place {
Place({required this.title, required this.image, required this.location})
: id = uuid.v4();
final String id;
final String title;
final File image;
final PlaceLocation location;
}
class Place {
Place({required this.title, required this.image, required this.location, String? id})
: id = id ?? uuid.v4();
final String id;
final String title;
final File image;
final PlaceLocation location;
}
db.query()
)가 끝난다면 해당 state를 main 에 붙여주기만 하면 된다.body: Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder(future: , builder:(context, snapshot) =>
snapshot.connectionState==ConnectionState.waiting
? const Center(child: CircularProgressIndicator())
: PlacesList(places: userPlaces)
),
),
class PlacesScreen extends ConsumerWidget {
const PlacesScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final userPlaces = ref.watch(userPlacesProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Your Places'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => const AddPlaceScreen(),
),
);
},
)
],
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder(
future: ,
builder: (context, snapshot) =>
snapshot.connectionState == ConnectionState.waiting
? const Center(child: CircularProgressIndicator())
: PlacesList(places: userPlaces),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:native_app/providers/user_places.dart';
import 'package:native_app/screens/add_place.dart';
import 'package:native_app/widgets/places_list.dart';
class PlacesScreen extends ConsumerStatefulWidget {
const PlacesScreen({super.key});
ConsumerState<PlacesScreen> createState() {
return _PlacesScreenState();
}
}
class _PlacesScreenState extends ConsumerState<PlacesScreen> {
late Future<void> _placesFutre;
void initState() {
super.initState();
_placesFutre = ref.read(userPlacesProvider.notifier).loadPlaces();
}
Widget build(BuildContext context) {
final userPlaces = ref.watch(userPlacesProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Your Places'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => const AddPlaceScreen(),
),
);
},
)
],
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder(
future: _placesFutre,
builder: (context, snapshot) =>
snapshot.connectionState == ConnectionState.waiting
? const Center(child: CircularProgressIndicator())
: PlacesList(places: userPlaces),
),
),
);
}
}