<img height="1" width="1" src="https://www.facebook.com/tr?id=292546644544618&amp;ev=PageView&amp;noscript=1">

Baza danych Sqlite w Xamarin.Forms

Wiele aplikacji potrzebuje miejsca do przechowywania danych. Jednym z możliwych rozwiązań jest wykorzystanie bazy danych SQLite. Sprawdź, w jaki sposób przystosować aplikację do wykorzystywania tej bazy danych.

W poprzednim artykule poznałeś wzorzec MVVM i zobaczyłeś jego zastosowanie w aplikacji pisanej w Xamarin.Forms. Teraz przyszedł czas na konfigurację bazy danych. W tym artykule wykorzystamy inną aplikację – TimeTracker. Jej zadaniem jest umożliwienie wpisywanie zadań, które realizowałeś w trakcie dnia.

Punkt wyjścia

Kod aplikacji możesz pobrać z repozytorium znajdującego się pod adresem https://bitbucket.org/Leroom/timetracker.git . Dobrym miejscem do startu jest tag v0.1.2. Aby się na niego przełączyć, wpisz w konsoli git

git checkout v0.1.2

W tym momencie aplikacja składa się z dwóch stron. Pierwsza z nich prezentuje listę elementów, zdefiniowanych bezpośrednio w kodzie. Kliknięcie na przycisk Dodaj na górnej belce okna spowoduje przeniesienie do drugiej strony – edytora wpisów. Kliknięcie na którykolwiek z dwóch elementów listy również przeniesie nas na stronę edytora, z tym że dodatkowo na stronie edytora będzie widoczny identyfikator klikniętego elementu.

Baza danych SQLite

SQLite to relacyjna baza danych. Jest ona powszechnie używana na platformach mobilnych. Dzięki SQLite, każde urządzenie jest dla siebie serwerem danych. Dane przetrzymywane są na dysku, w pliku binarnym. Każda tabela i indeks jest przechowywana w formie osobnego B-drzewa. SQLite wspiera transakcje ACID, klauzule ORDER BY, GROUP BY, DISTINCT, wyszukiwanie pełnotekstowe I wiele więcej.

Konfiguracja SQLite

Aby nasza aplikacja mogła przetrzymywać dane w bazie Sqlite, do naszego projektu musimy dołączyć odpowiednie paczki NuGet. Aby dołączyć paczkę do projektu kliknij prawym przyciskiem myszy na Solution w Solution Explorerze, a następnie wybierz „Manage NuGet packages”.

1.png

Przejdź do zakładki Browse i wyszukaj paczkę SQLite.Net-PCL. Upewnij się, że wybierasz dobrą paczkę (taką, jak na zrzucie poniżej)

2.png

Następnie zaznacz wszystkie projekty w oknie „Manage Packages for Solution” i wciśnij Install. Paczka SQLite.Net.Core-PCL powinna doinstalować się automatycznie, jako wymagana zależność. Teraz zainstaluj paczkę SQLite.Net.Async PCL – zapytania bazodanowe powinny być wykonywane asynchronicznie.

Jeśli chcesz testować aplikację UWP na maszynie lokalnej, musisz zainstalować SQLite for Universal Windows Platform. W Visual Studio przejdź do Tools -> Extensions and Updates, następnie do zakładki online i wyszukaj SQLite for Universal Windows Platform.

3.png

Po instalacji wymagany będzie restart Visual Studio. Gdy wrócisz do projektu, będziesz musiał dołączyć referencję do SQLite w projekcie UWP. Przejdź do projektu UWP i wejdź w tryb dodawania referencji (prawy przycisk myszy na References, następnie Add Reference). W oknie Reference Managera przejdź do Universal Windows, a następnie Extensions. Powinieneś znaleźć tam SQLite for Universal Windows Platform. Zaznacz tą pozycję i wciśnij OK.

Najtrudniejsze za nami. Teraz czas na przyjemną część – dodanie kodu odpowiedzialnego za operacje bazodanowe. Ale wcześniej musisz dowiedzieć się co nieco o…

DependencyService

DependencyService pozwala na użycie w kodzie współdzielonym komponentów wykorzystujących natywne rozwiązania poszczególnych platform. Natywne komponenty muszą jedynie implementować wspólny interfejs, zdefiniowany w kodzie współdzielonym oraz muszą być zarejestrowane w odpowiedni sposób. Wykorzystanie tego mechanizmu zapewnia dostęp do wszystkich możliwości oferowanych przez poszczególne platformy.

Szczegółowy i aktualny opis znajdziesz w oficjalnej dokumentacji Xamarina.

DependencyService używa wzorca (przez wielu uważanego za antywzorzec) Service Locator. Ten artykuł ma na celu jedynie pokazanie możliwości użycia DependencyService. W kolejnym artykule zamienimy użycia DependencyService’u na rozwiązywanie komponentów za pomocą kontenera DI.

Bazy danych w projekcie TimeTracker

Baza danych to najogólniej mówiąc plik na dysku. Aby wykonywać operacje na tym pliku będziemy potrzebowali ścieżkę, pod którą jest on zapisany. Każda platforma ma swój sposób na określenie miejsca, w którym zapisywane są pliki aplikacji. Oznacza to, że musimy napisać dla każdej platformy osobny komponent do pobierania ścieżki do pliku. Mając wiedzę o DependencyService to zadanie nie stanowi problemu.

Na początku, w projekcie PCL definiujemy interfejs

public interface ILocalFilePathProvider
{
    string GetLocalPathToFile(string fileName);
}

Teraz zaimplementujemy ten interfejs w projektach platformowych. Zwróć uwagę na atrybut Dependency – w taki sposób rejestrujemy komponenty, aby ServiceLocator był w stanie je rozwiązać.

Implementacja na Androida:

using System;
using System.IO;
using TimeTracker.Droid;
using Xamarin.Forms;
[assembly: Dependency(typeof(LocalFilePathProvider))]
namespace TimeTracker.Droid
{
    public class LocalFilePathProvider : ILocalFilePathProvider
    {
        public string GetLocalPathToFile(string fileName)
        {
            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), fileName);
        }
    }
}

Implementacja na UWP

using System.IO;
using Windows.Storage;
using TimeTracker.UWP;
using Xamarin.Forms;
[assembly: Dependency(typeof(LocalFilePathProvider))]
namespace TimeTracker.UWP
{
    public class LocalFilePathProvider : ILocalFilePathProvider
    {
        public string GetLocalPathToFile(string fileName)
        {
            return Path.Combine(ApplicationData.Current.LocalFolder.Path, fileName);
        }
    }
}

Implementacja na iOS

using System;
using System.IO;
using TimeTracker.iOS;
using Xamarin.Forms;
[assembly: Dependency(typeof(LocalFilePathProvider))]
namespace TimeTracker.iOS
{
    public class LocalFilePathProvider : ILocalFilePathProvider
    {
        public string GetLocalPathToFile(string fileName)
        {
            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), fileName);
        }
    }
}

Teraz pora na utworzenie obiektu bazy danych. Na potrzeby tego artykułu baza danych będzie statyczna i dostępna w całej aplikacji. W kolejnych artykułach, gdy już skonfigurujemy kontener DI, dostęp do bazy danych będzie możliwy jedynie przy użyciu odpowiedniego komponentu. Ale na razie pozwólmy sobie na proste rozwiązanie. Utwórz klasę TrackingsDatabase, która będzie odpowiadała za zarządzanie połączeniem do bazy danych. Na razie wystarczy konstruktor przyjmujący w parametrze nazwę pliku, w którym przechowujemy dane.

public class TrackingsDatabase
{
    public TrackingsDatabase(string databaseName)
    {
        var pathToDatabaseFile = DependencyService.Get<ILocalFilePathProvider>().GetLocalPathToFile(databaseName);
    }
}

Widzimy tu wykorzystanie DependencyService’u do pobrania ścieżki do pliku odpowiedniej dla poszczególnych platform. Teraz dodajmy obiekt odpowiedzialny za obsługę bazy danych do aplikacji.

Do pliku App.xaml.cs dodaj poniższy kod

private static TrackingsDatabase _database;
public static TrackingsDatabase Database => GetDatabase();

private static TrackingsDatabase GetDatabase()
{
    if (_database == null)
    {
        _database = new TrackingsDatabase("Database.db3");
    }

    return _database;
}

Powyższy kod zapewnia dostęp do obiektu bazy danych w obrębie całej aplikacji. Teraz dodajmy utworzenie połączenia asynchronicznego do SQLite. Zmodyfikuj konstruktor TrackingsDatabase do postaci

public TrackingsDatabase(string databaseName)
{
    var pathToDatabaseFile = DependencyService.Get().GetLocalPathToFile(databaseName);
    var platform = DependencyService.Get().GetPlatform();

    _connection = new SQLiteAsyncConnection(() =>
        new SQLiteConnectionWithLock(platform, new SQLiteConnectionString(pathToDatabaseFile, false)));
    _connection.CreateTableAsync().Wait();
}

private readonly SQLiteAsyncConnection _connection;

W powyższym kodzie widnieje nowy komponent – ISqlitePlatformProvider – którego jeszcze nie zaimplementowaliśmy. Poniższe 4 listingi będą zawierały całą konfigurację PlatformProviderów.

W projekcie PCL dodaj interfejs

{
    ISQLitePlatform GetPlatform();
}

W projekcie Android dodaj implementację


using SQLite.Net.Interop;
using SQLite.Net.Platform.XamarinAndroid;
using TimeTracker.Droid;
using Xamarin.Forms;
[assembly: Dependency(typeof(SqlitePlatformProvider))]
namespace TimeTracker.Droid
{
    public class SqlitePlatformProvider : ISqlitePlatformProvider
    {
        public ISQLitePlatform GetPlatform()
        {
            return new SQLitePlatformAndroid();
        }
    }
}

W projekcie UWP dodaj implementację

using SQLite.Net.Interop;
using SQLite.Net.Platform.WinRT;
using TimeTracker.UWP;
using Xamarin.Forms;
[assembly: Dependency(typeof(SqlitePlatformProvider))]
namespace TimeTracker.UWP
{
    public class SqlitePlatformProvider : ISqlitePlatformProvider
    {
        public ISQLitePlatform GetPlatform()
        {
            return new SQLitePlatformWinRT();
        }
    }
}

W projekcie iOS dodaj implementację

using SQLite.Net.Interop;
using SQLite.Net.Platform.XamarinIOS;
using TimeTracker.iOS;
using Xamarin.Forms;
[assembly: Dependency(typeof(SqlitePlatformProvider))]
namespace TimeTracker.iOS
{
    public class SqlitePlatformProvider : ISqlitePlatformProvider
    {
        public ISQLitePlatform GetPlatform()
        {
            return new SQLitePlatformIOS();
        }
    }
}

Zanim przejdziemy do wykorzystania bazy danych zmodyfikujmy jeszcze TrackingModel, aby silnik bazy danych wiedział, który atrybut odpowiada za klucz główny. Nad właściwością Id dodajmy atrybut [PrimaryKey]. Taka konfiguracja zapewnia nam poprawne utworzenie struktury bazy danych oraz połączenie do niej. Teraz przyszedł czas na modyfikację kodu, aby faktycznie wykorzystywać bazę danych.

Używanie bazy danych

The code for using the database is available when you type the string below in the git console


git checkout v0.1.5

Na uwagę zasługuje sposób ładowania danych. W plikach .cs stron nadpisana jest metoda OnAppearing, a w niej jest wywołanie metody Load z ViewModelu. W metodach Load powinna być zawarta logika pozwalająca przygotować dane dla ekranu – pobrać je z bazy danych lub wypełnić ViewModel danymi inicjalnymi.

Do klasy TrackingsDatabase dodane zostały metody Save, Get i GetAll pozwalające na wykonanie wszystkich potrzebnych w tym momencie operacji.

Metoda Save zapisuje zmiany dokonane na edytorze, metoda Get pobiera element wybrany na stronie z listą wpisów, natomiast metoda GetAll pobiera wszystkie wpisy.

Przeglądanie pliku bazy danych

Istnieje możliwość podejrzenia zawartości bazy danych, która powstała w momencie działania aplikacji na maszynie lokalnej. Aby przejrzeć aktualny plik bazy danych możemy wykorzystać program SQLite Studio. Uruchom program i dodaj nową bazę danych. Plik, który nas interesuje znajdziesz poprzez przejście do folderu %USERPROFILE%/AppData/Local/Packages i wyszukanie tam pliku Database.db3. Po wykonaniu tej akcji, będziesz mógł pisać zapytania bezpośrednio w programie SQLite Studio.

Podsumowanie

Masz w ręku aplikację pozwalającą zapisywać, na co przeznaczasz swój cenny czas. W kolejnych artykułach poprawimy trochę jakość kodu. Zamienimy użycie DependencyService’u na rozwiązywanie komponentu za pomocą kontrenera DI oraz zmienimy mechanizm przechodzenia na inne strony.

 

Xamarin.Forms, Xamarin, SQLite
Marcin Lach

Marcin is a Senior Software Developer at ITMAGINATION and has over 4 years of experience in programming. One of his responsibilities at ITMAGINATION is creating mobile applications utilizing Xamarin.Forms. He is focused on delivering best quality software fitting client’s needs.

Chcesz wiedzieć więcej? Skontaktuj się z nami.