Search code examples
c#databasesqlite.net-coremaui

Unable to retrieve data from existing database


I have a SQLite database file called cbt.db in the Data folder in my .NET MAUI project. I'm trying to retrieve a collection of questions and answers from this database, but whenever I run the app I get a

System.NullReferenceException

on the line

var questions = await _dBService.GetQuestionsAsync();

in the QuizViewModel.

QuizViewModel.cs:

namespace CBT.ViewModels;

public partial class QuizViewModel : ObservableObject
{
    private IDBService _dBService;
    private int _currentQuestionIndex = 1;

    public ObservableCollection<Question> Questions { get; } = new();

    [ObservableProperty]
    public Question? currentQuestion;

    public QuizViewModel(IDBService dBService)
    {
        _dBService = dBService;
    }

    [RelayCommand]
    public static async Task<QuizViewModel> CreateAsync(IDBService dBService)
    {
        var viewModel = new QuizViewModel(dBService);
        await viewModel.GetQuestionsAsync();
        viewModel.CurrentQuestion = viewModel.Questions[viewModel._currentQuestionIndex];

        return viewModel;
    }

    [RelayCommand]
    async Task GetQuestionsAsync()
    {
        try
        {
            var questions = await _dBService.GetQuestionsAsync();

            if (questions.Count != 0)
            {
                Questions.Clear();
            }

            foreach(var question in questions)
            {
                Questions.Add(question);
            }
        } 
        catch(Exception ex)
        {
            Debug.Print($"Unable to get questions: {ex.Message}");
        }
    }
    
    // Other methods go here...
}

DBService.cs:

namespace CBT.Services;

class DBService : IDBService
{
    private readonly SQLiteAsyncConnection _database;
    public static string DBPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "cbt.db");

    // The constructor initializes the database connection and creates the table if not exists
    public DBService()
    {
        LoadDB();
        _database = new SQLiteAsyncConnection(DBPath, Constants.Flags);
        _database.CreateTableAsync<Question>();
    }

    public void LoadDB()
    {
        if (!File.Exists(DBPath))
        {
            var assembly = IntrospectionExtensions.GetTypeInfo(typeof(App)).Assembly;
            using Stream? stream = assembly.GetManifestResourceStream("CBT.Data.cbt.db");

            using MemoryStream? memoryStream = new();
            stream.CopyTo(memoryStream);
            File.WriteAllBytes(DBPath, memoryStream.ToArray());
            Debug.WriteLine($"{stream}");
        }
    }

    public async Task<List<Question>> GetQuestionsAsync()
    {
        return await _database.Table<Question>().ToListAsync();
    }

    // Other CRUD operations go here
}

MauiProgram.cs:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        
        builder.Services.AddSingleton<IDBService, DBService>();
        builder.Services.AddSingleton<INavigationService, NavigationService>();

        builder.Services.AddTransient<QuizViewModel>();
        builder.Services.AddTransient<QuizPage>();

        return builder.Build();
    }
}

Solution

  • I finally figured out a solution. I was calling the DBService before its initialization, so I initialized the QuizViewModel in the QuizPage and passed it the DBService. Here is what it looked like.

    QuizPage.xaml.cs

    public partial class QuizPage : ContentPage
    {
        private QuizViewModel _viewModel;
        public QuizPage(QuizViewModel quizViewModel)
        {
            InitializeComponent();
            InitializeViewModel();
        }
    
        private async void InitializeViewModel()
        {
            // Create an instance of IDBService
            var dbService = new DBService();
    
            // Create an instance of QuizViewModel using CreateAsync method
            _viewModel = await QuizViewModel.CreateAsync(dbService);
    
            BindingContext = _viewModel;
        }
    }