DataStore — ένα στοιχείο από τη βιβλιοθήκη Jetpack που προορίζεται για την αποθήκευση μικρών όγκων δεδομένων σε εφαρμογές Android. Σε αντίθεση με τα SharedPreferences, λειτουργεί ασύγχρονα και εγγυάται τη συνέπεια των δεδομένων κατά την ταυτόχρονη πρόσβαση. Σύμφωνα με στοιχεία της Google, 2024, το DataStore χρησιμοποιεί Kotlin Coroutines και Flow, καθιστώντας το ασφαλές για το κύριο νήμα και κατάλληλο για αντιδραστικές αρχιτεκτονικές.
Κύρια σημεία
DataStore — μια λύση από την Google για τοπική αποθήκευση δεδομένων στο Android, που παρουσιάστηκε το 2020 ως εναλλακτική των SharedPreferences. Υποστηρίζει δύο λειτουργίες: Preferences DataStore (απλά ζεύγη κλειδιού-τιμής) και Proto DataStore (τυποποιημένο σχήμα βασισμένο σε Protocol Buffers).
Το κύριο πλεονέκτημα — πλήρης ασυγχρονία: όλες οι λειτουργίες ανάγνωσης επιστρέφουν Flow από Kotlin Coroutines, και η εγγραφή εκτελείται σε περιβάλλον coroutine. Αυτό εξαλείφει το μπλοκάρισμα του κύριου νήματος, που ήταν ένα τυπικό πρόβλημα των SharedPreferences κατά την εργασία με μεγάλους όγκους δεδομένων.
Το DataStore εγγυάται την ατομικότητα των λειτουργιών: οι ταυτόχρονες εγγραφές δεν οδηγούν σε απώλεια δεδομένων χάρη στο μοντέλο συναλλαγών. Εάν δύο στοιχεία τροποποιούν ταυτόχρονα την ίδια τιμή, το DataStore χειρίζεται σωστά τη σύγκρουση μέσω του μηχανισμού compare-and-swap.
Σύμφωνα με στοιχεία του Google I/O 2023, το DataStore χρησιμοποιείται στο 40% των νέων έργων στο Android, και η Google συνιστά μετανάστευση από SharedPreferences σε όλες τις εφαρμογές όπου απαιτείται σταθερότητα αποθήκευσης ρυθμίσεων.
Στη βάση του DataStore βρίσκεται το SingleProcessDataStore — μια υλοποίηση που λειτουργεί στο πλαίσιο μιας διεργασίας. Χρησιμοποιεί αποθήκευση αρχείων με κλειδώματα σε επίπεδο αρχείου: κατά την εγγραφή δεδομένων, το αρχείο κλειδώνεται, αποτρέποντας τη φθορά κατά την ταυτόχρονη πρόσβαση.
Το DataStore χειρίζεται αυτόματα σφάλματα αποσειριοποίησης: εάν το αρχείο είναι κατεστραμμένο, επιστρέφει την προεπιλεγμένη τιμή και ξαναγράφει το αρχείο. Αυτή η συμπεριφορά διαμορφώνεται μέσω του corruptionHandler, που μπορεί να οριστεί κατά τη δημιουργία του DataStore.
SharedPreferences υποφέρει από τρία θεμελιώδη προβλήματα: σύγχρονη ανάγνωση από το δίσκο στο κύριο νήμα, έλλειψη εγγυήσεων ατομικότητας σε ταυτόχρονες εγγραφές και αδυναμία αντιδραστικής παρακολούθησης αλλαγών. Το DataStore λύνει και τα τρία: Flow για παρατήρηση, κλείδωμα αρχείου για ατομικότητα και ασύγχρονο API για ασφάλεια νημάτων.
DataStore αποθηκεύει δεδομένα σε αρχεία στην εσωτερική μνήμη της συσκευής. Το Preferences DataStore χρησιμοποιεί μορφή αρχείου παρόμοια με τα SharedPreferences, αλλά με πρόσθετα μεταδεδομένα για έλεγχο ακεραιότητας. Το Proto DataStore χρησιμοποιεί τη δυαδική μορφή Protocol Buffers, που μειώνει το μέγεθος αρχείου και επιταχύνει τη σειριοποίηση.
Κατά την ανάγνωση δεδομένων, το DataStore φορτώνει ολόκληρο το αρχείο στη μνήμη μία φορά, μετά την οποία οι συνδρομητές λαμβάνουν την τρέχουσα κατάσταση μέσω Flow. Οι αλλαγές μεταδίδονται αυτόματα σε όλους τους ενεργούς συνδρομητές — δεν απαιτείται χειροκίνητη εγγραφή listener, όπως στα SharedPreferences.
Preferences DataStore χρησιμοποιεί έναν ενσωματωμένο μηχανισμό σειριοποίησης βασισμένο σε Map. Κάθε καταχώρηση είναι ένα ζεύγος συμβολοσειράς και πρωτόγονου τύπου (Int, Boolean, Float, Long, String, Set). Τα δεδομένα αποθηκεύονται σε αρχείο XML, παρόμοιο με SharedPreferences, αλλά με ατομική εγγραφή μέσω κλειδώματος αρχείου.
Παράδειγμα δημιουργίας Preferences DataStore: η επέκταση preferencesDataStore στο Context δημιουργεί ένα singleton με το όνομα αρχείου. Σε επαναλαμβανόμενες κλήσεις επιστρέφεται το ίδιο στιγμιότυπο — αυτό εξαλείφει τον διπλασιασμό αρχείων και τη σύγχυση με διαφορετικά στιγμιότυπα αποθηκευτικού χώρου.
Proto DataStore απαιτεί τον ορισμό σχήματος δεδομένων μέσω αρχείου .proto και μεταγλώττιση με χρήση του protobuf plugin. Η παραγόμενη κλάση Java χρησιμοποιείται ως μοναδικό σημείο εισόδου για όλα τα πεδία — αυτό εξαλείφει τυπογραφικά λάθη σε κλειδιά, τυπικά για SharedPreferences.
Το σχήμα Proto DataStore ορίζεται μία φορά και υποστηρίζει την προσθήκη νέων πεδίων χωρίς απώλεια παλαιών δεδομένων. Εάν στη νέα έκδοση της εφαρμογής προστεθεί ένα πεδίο με προεπιλεγμένη τιμή, το παλιό αρχείο θα αποσειριοποιηθεί σωστά — η προς τα πίσω συμβατότητα είναι ενσωματωμένη στο πρωτόκολλο.
Η επιλογή μεταξύ Preferences DataStore και Proto DataStore εξαρτάται από την πολυπλοκότητα των δεδομένων και τις απαιτήσεις τυποποίησης. Και οι δύο παραλλαγές είναι ασύγχρονες και συναλλακτικές, αλλά διαφέρουν στο επίπεδο type-safety και στην απόδοση σειριοποίησης.
| Χαρακτηριστικό | Preferences DataStore | Proto DataStore |
|---|---|---|
| Τυποποίηση | Ασθενής (κλειδί-τιμή) | Ισχυρή (παραγόμενη κλάση) |
| Σειριοποίηση | XML (ενσωματωμένη) | Protocol Buffers (protobuf) |
| Μέγεθος αρχείου | Μεγάλο (αναγνώσιμο XML) | Μικρό (δυαδικό) |
| Πολυπλοκότητα | Χαμηλή (χωρίς .proto) | Μεσαία (απαιτεί .proto) |
| Μετανάστευση σχήματος | Δεν υπάρχει σχήμα | Αυτόματη (proto) |
| Συμβατότητα | SharedPreferences (μέσω μετανάστευσης) | Μόνο Proto DataStore |
Preferences DataStore είναι κατάλληλο για απλές ρυθμίσεις: σημαίες ενεργοποίησης λειτουργιών, συμβολοσειρά διακριτικού εξουσιοδότησης, αριθμός εκκινήσεων εφαρμογής. Εάν τα δεδομένα είναι λίγα (έως 10–15 κλειδιά) και δεν απαιτούν αυστηρό σχήμα — το Preferences DataStore παρέχει ελάχιστο όριο εισόδου χωρίς σύνδεση του protobuf plugin.
Proto DataStore δικαιολογείται όταν η δομή δεδομένων είναι πολύπλοκη ή μπορεί να αλλάξει μεταξύ εκδόσεων εφαρμογής. Για παράδειγμα, ρυθμίσεις προφίλ χρήστη ή διαμόρφωση δοκιμών A/B με 20+ πεδία. Το Protobuf παρέχει ισχυρή τυποποίηση και αυτόματες μεταναστεύσεις, που εξαλείφει σφάλματα χρόνου εκτέλεσης λόγω αναντιστοιχίας κλειδιών.
Google παρέχει ενσωματωμένο μηχανισμό μετανάστευσης μέσω της κλάσης SharedPreferencesMigration. Η μετανάστευση εκτελείται μία φορά κατά την πρώτη εκκίνηση μετά την ενημέρωση της εφαρμογής: το DataStore διαβάζει δεδομένα από SharedPreferences, τα γράφει στη δική του μορφή και σηματοδοτεί τη μετανάστευση ως ολοκληρωμένη.
Η μετανάστευση υποστηρίζει προσαρμοσμένους μετασχηματισμούς: εάν στα SharedPreferences τα κλειδιά δεν ταιριάζουν με τα επιθυμητά κλειδιά DataStore, μπορεί να οριστεί μια συνάρτηση μετασχηματισμού μέσω SharedPreferencesMigration. Αυτό επιτρέπει τη μετονομασία κλειδιών και την αλλαγή τύπων δεδομένων κατά τη διαδικασία μετανάστευσης.
Πρώτο βήμα: προσθέστε το DataStore στο build.gradle και δημιουργήστε ένα στιγμιότυπο DataStore με μετανάστευση: το SharedPreferencesMigration δέχεται το όνομα του αρχείου SharedPreferences και το σύνολο κλειδιών που πρέπει να μεταφερθούν. Δεύτερο βήμα: αφαιρέστε όλο τον κώδικα που λειτουργεί μέσω SharedPreferences και αντικαταστήστε τον με κλήσεις DataStore. Τρίτο — ελέγξτε τη μετανάστευση: κατά την πρώτη εκκίνηση τα δεδομένα πρέπει να εμφανιστούν στο DataStore και το παλιό αρχείο SharedPreferences πρέπει να σταματήσει να χρησιμοποιείται.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore ενσωματώνεται εύκολα σε ένα υπάρχον έργο. Παρακάτω δίνονται πρακτικά παραδείγματα για Preferences DataStore και Proto DataStore — και τα δύο δείχνουν ανάγνωση, εγγραφή και αντιδραστική παρατήρηση δεδομένων.
Σε αυτό το παράδειγμα το Preferences DataStore αποθηκεύει τρεις ρυθμίσεις: σκοτεινό θέμα, όνομα χρήστη και αριθμό εκκινήσεων. Η ανάγνωση γίνεται μέσω της επέκτασης .data, που επιστρέφει Flow. Η εγγραφή — μέσω της συνάρτησης suspend .edit, που εγγυάται την ατομικότητα των αλλαγών.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore απαιτεί τον ορισμό ενός αρχείου .proto. Μετά τη μεταγλώττιση, δημιουργείται η κλάση UserSettings, που χρησιμοποιείται για ανάγνωση και εγγραφή. Οι μεταναστεύσεις εκδόσεων σχήματος περιγράφονται στο ίδιο αρχείο .proto και εφαρμόζονται αυτόματα.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Ανάγνωση από το DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Εγγραφή νέων τιμών
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore ενσωματώνεται με την αρχιτεκτονική MVVM μέσω ViewModel. Το Flow από το DataStore συλλέγεται μέσω .stateIn και χρησιμοποιείται στο UI. Σε κάθε αλλαγή δεδομένων, το UI ενημερώνεται αυτόματα — δεν απαιτούνται χειροκίνητες ενημερώσεις ή LiveData.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Συχνές ερωτήσεις
DataStore λειτουργεί ασύγχρονα (δεν μπλοκάρει το νήμα UI), υποστηρίζει ταυτόχρονη πρόσβαση μέσω συναλλαγών και επιτρέπει αντιδραστική εγγραφή σε αλλαγές μέσω Flow. SharedPreferences — σύγχρονο API με κίνδυνο ANR σε μεγάλους όγκους δεδομένων και χωρίς ενσωματωμένη υποστήριξη αντιδραστικότητας.
DataStore είναι γραμμένο σε Kotlin και απαιτεί Kotlin Coroutines. Η χρήση του από Java είναι δυνατή αλλά άβολη: πρέπει να δημιουργηθούν περιτυλίγματα με CompletableFuture ή να γίνει χειροκίνητη διαχείριση coroutines. Για έργα Java, η Google συνιστά να διατηρήσετε SharedPreferences ή να προσθέσετε Kotlin στην ενότητα.
DataStore φορτώνει ολόκληρο το αρχείο στη μνήμη κατά την ανάγνωση, επομένως δεν είναι κατάλληλο για αποθήκευση λιστών ή μεγάλων αντικειμένων. Για τέτοια σενάρια, χρησιμοποιήστε Room ή SQLite. Το DataStore είναι βελτιστοποιημένο για ρυθμίσεις και μικρά δομημένα δεδομένα — έως εκατοντάδες kilobyte.
Κατά τη δημιουργία DataStore μπορεί να μεταβιβαστεί ένα corruptionHandler — μια συνάρτηση που καλείται όταν το αρχείο είναι κατεστραμμένο. Από προεπιλογή, το DataStore ρίχνει εξαίρεση CorruptionException. Στο corruptionHandler μπορούν να επιστραφούν κενά δεδομένα, μετά από τα οποία το DataStore ξαναγράφει το αρχείο με τη σωστή κατάσταση.
Ναι, το Proto DataStore απαιτεί τον ορισμό σχήματος σε αρχείο .proto και τη σύνδεση του protobuf-gradle-plugin. Εάν το έργο είναι μικρό και τα δεδομένα απλά, είναι ευκολότερο να χρησιμοποιήσετε Preferences DataStore — δεν απαιτεί πρόσθετη διαμόρφωση build.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης