Flutter: Ukládání dat
Z MiS
Pro trvalé uložení dat i po vypnutí aplikace můžeme data uložit:
- na lokální disk konkrétního zařízení
- do síťového úložiště (databáze, cloudové úložiště)
- můžeme rozdělit aplikaci na frontend a backend, kde data budou uložena v backendu (opět obvykle v databázi).
Následuje několik příkladů možných řešení.
Obsah |
Lokální export: jsonEncode/jsonDecode
Vlastnosti
- Formát JSON lze zpracovávat i v dalších aplikacích – je vhodný pro výměnu dat mezi aplikacemi a je textový.
- Jednoduše lze uložit seznam objektů. Složitější vnořované struktury objektů je třeba ručně převádět.
- Není vhodné pro větší objem dat – převod na text může zvětšit objem dat.
- Vhodné zejména pro textová data: texty, celá čísla, logické hodnoty. Méně vhodné pro desetinná čísla, obrázky, ... z důvodu nutné konverze na text.
- Formát JSON neukládá datové typy.
- Výsledek je uložen lokálně – převod z mobilní aplikace na desktop apod. je třeba řešit ručně.
Postup zápisu
- Nalezení umístění souboru:
- Využij balíček
path_providera metodugetApplicationDocumentsDirectory(). - Pro zápis do souboru použij třídu
Filea metoduwriteAsString.
- Využij balíček
- Převod dat na JSON
- Využij balíček
dart:convert. - Pro parsování JSON využij metodu
jsonDecode(list)– výsledkem bude textová reprezentace objektu ve formátu JSON.
- Využij balíček
Postup čtení
- Nalezení umístění souboru:
- Využij balíček
path_providera metodugetApplicationDocumentsDirectory(). - Pro čtení souboru použij třídu
Filea metodureadAsString.
- Využij balíček
- Převod dat na JSON
- Využij balíček
dart:convert. - Pro ukládání využij metodu
jsonEncode(list)– výsledkem bude textová reprezentace objektu ve formátu JSON.
- Využij balíček
Ukázka použití v aplikaci
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Úložiště Demo',
home: HomePage(storage: FileStorage()),
);
}
}
class Zaznam {
final String jmeno;
final int pocet;
const Zaznam({ required this.jmeno, required this.pocet, });
// Převod do JSON
Map<String, dynamic> toJson() => {
'jmeno': jmeno,
'pocet': pocet,
};
// Obnov z JSON
factory Zaznam.fromJson(Map<String, dynamic> json) {
return Zaznam(
jmeno: (json['jmeno']),
pocet: json['pocet'] as int,
);
}
}
/// Práce se soubory
class FileStorage {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/data.txt');
}
Future<String> readFile() async {
final file = await _localFile;
return file.readAsString();
}
Future<void> writeFile(String content) async {
final file = await _localFile;
await file.writeAsString(content);
}
}
/// Stav aplikace včetně zápisu do souboru
class HomePage extends StatefulWidget {
final FileStorage storage;
const HomePage({required this.storage});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Zaznam> _seznam = [];
@override
void initState() {
super.initState();
_loadData(); // Načtení dat při startu
}
Future<void> _loadData() async {
try {
final jsonString = await widget.storage.readFile();
final List<dynamic> decoded = jsonDecode(jsonString);
setState(() {
_seznam = decoded
.map((item) => Zaznam.fromJson(item as Map<String, dynamic>))
.toList();
});
} catch (e) {
TODO: Zobraz chybové hlášení vhodným způsobem.
}
}
Future<void> _saveData() async {
final jsonString = jsonEncode(
_seznam.map((z) => z.toJson()).toList()
);
await widget.storage.writeFile(jsonString);
}
void _addRecord() {
setState(() {
_seznam.add(Zaznam(
jmeno: 'Josef',
pocet: 1000,
));
});
_saveData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// ... vytvoř GUI
);
}
}
Tip
- Pro větší objemy dat či komplexnější data lze pro export do formátu JSON využít balíček
json_serializable.
Zdroje
- HatchJS.com > How to Convert a List to JSON in Flutter
- Flutter Docs > JSON and serialization
- Flutter Docs > Read and write files
Lokální databáze: Hive
- Data ukládá binárně, ale stále na lokální zařízení.
Zdroje
Google Firestore
- Google Firestore je cloudová NoSQL databáze od Google, která je součástí platformy Firebase.
- Vlastnosti
- Firebase je dobře integrovaná do frameworku Flutter (Flutter také vyvíjí Google)
- Data se ukládají do cloudu – data může sdílet mobilní i webová aplikace
- Synchronizace dat v reálném čase
- Pro malé projekty stačí varianta zdarma (pro větší bude třeba platit)
Struktura dat ve Firestore
- Data se neukládají do entit jako u ER databází – spíše jako několik seznamů objektů.
- Pojmy
- Dokument (Document) ... konkrétní záznam (jako jeden řádek v databázi) – například údaje o jednom uživateli.
- Pole (Field) ... konkrétní hodnota (atribut) – například jméno uživatele
- Kolekce (Collection) ... odpovídá entitě v ER databázích – je to vlastně seznam dokumentů
- Databáze (Database) ... skupina souvisejících kolekcí
- Projekt (Project) ... jeden projekt obvykle odpovídá jednomu softwarovému produktu. Může zahrnovat různé podoby jedné aplikace:
- IOS
- Android
- webová aplikace
- Unity
- Flutter multiplatformní aplikace
Příklad:
users (kolekce)
└── user1 (dokument)
├── name: "Jan"
├── age: 18
└── email: "jan@email.cz"
Vytvoření projektu ve Firebase
- Jdi na: Console.firebase.google.com
- Klikni na Vytvořit projekt
- Zadej název projektu
- Po vytvoření přidej aplikaci (Android / iOS / Web)
Propojení Flutter aplikace s Firebase
Ve Flutter projektu spusť:
flutter pub add firebase_core flutter pub add cloud_firestore
Poté inicializuj Firebase v main.dart:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Ukládání dat do Firestore
- Importuj Firestore
import 'package:cloud_firestore/cloud_firestore.dart';
- Vytvoření instance pro přístup k databázi
final FirebaseFirestore db = FirebaseFirestore.instance;
- Přidání nového dokumentu
await db.collection('users').add({
'name': 'Jan',
'age': 18,
'email': 'jan@email.cz',
});
Tím se:
- vytvoří kolekce users (pokud neexistuje)
- přidá nový dokument s automatickým ID
- Uložení dokumentu s vlastním ID
await db.collection('users').doc('student1').set({
'name': 'Petra',
'age': 17,
});
Čtení dat z Firestore jednorázově
var snapshot = await db.collection('users').get();
for (var doc in snapshot.docs) {
print(doc.data());
}
Načtení v reálném čase
Firestore umožňuje poslouchat změny. Data se pak automaticky aktualizují, pokud proběhne změna v databázi (například z jiného zařízení připojeného ke stejnému projektu):
StreamBuilder(
stream: db.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
var docs = snapshot.data!.docs;
return ListView(
children: docs.map((doc) {
return ListTile(
title: Text(doc['name']),
);
}).toList(),
);
},
);
Aktualizace dat
await db.collection('users').doc('student1').update({
'age': 18,
});
Smazání dat
- Smazání dokumentu
await db.collection('users').doc('student1').delete();
Bezpečnostní pravidla (Rules)
Ve Firebase konzoli nastav pravidla přístupu.
Například pouze pro přihlášené uživatele:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Zdroje
- Základ sekce o Firebase byl vytvořen pomocí služby ChatGPT.
- YouTube.com > Get to know Cloud Firebase (seriál videí)
- Google Cloud > Firestore Pricing
Vlastní backend: ServerPod
- Framework, který umožňuje vytvořit projekt Flutteru se dvěma částmi: backendem a frontendem.
- Backend je následně možné spustit na svém serveru.
- Frontend běží na desktopu.