Tuesday, October 11, 2022

Handling Response of REST API

Let's say we need to use some REST api in our application. This api give an response object like below:

  • Object:
    • isSuccess
    • message
    • result
For example, we use a blogger service for our blog, and let's say this service provides some features like recent posts, statistics of post, etc. This features has fields that defined by publisher of the api. We should know these fields of the features, so we can implement this our project. We need to create a response class and response objects according to the fields of the features:
public class Response<T>
{
    public bool isSuccess { get; set; }
    public string message { get; set; }
    public T result { get; set; }
}

// we created response object classes according to the api
public class RecentPostResponse 
{
    public int postId { get; set; }
    public string title { get; set;}
    public string author { get; set; }
    public string publishedDate { get; set; }
}

public class PostStatisticResponse
{
    public int postId { get; set; }
    public int viewCounter { get; set; }
}
We have to create request method to use the api. Probably, the publisher will give an username and password or token.
private Response<T> SendRequest<T>()
{
	...
    
    string responseContent = ...
    
    Response<T> response = Json.DeserializeObject<Response<T>>(responseContent);
    
    return response;
}
I can use the api. I want to get recent post list via the api:
...

Response<List<RecentPostResponse>> response = SendRequest<List<RecentPostResponse>>();

List<RecentPostResponse> recentPostList = response.result;

...
That's how we can handle it.😟

Saturday, October 8, 2022

Making To-do App with .NET MAUI

First note as an information, the blogger of this blog is not profession about .NET MAUI. There is going to be long post for a making to-do app. Because this is not about just to-do application itself. MVVM will be used for project structure, and also the application can save to local database. We are going use SQLite database engine. Because it is simple, and the project is not complex. 

# MVVM is software architectural pattern. 
  • Model
  • View 
  • Viewmodel
Basically, this pattern makes more manageable, readable, and reusable a code project. You have to use it, but it recommended for .NET MAUI apps. Model is about data. A models represent a table in database. If we are going to make a todo application, then we need to create model that called to-do within models' folder in the project directory. Yes, we are going to create three folders that called models, views, viewmodels in the project. The first lights of this decoupling.

View is about user interface of the application. This side just shows necessary animation, other stuffs and data to user. For example, I'm going to add checkbox for to-do app. So actually I will do it in views folder. If I make changes on UI then I have to go Views as part of the project.

Viewmodels are logical structures that provide a relationship between the view and the model. This is the main part of programming (for me👶). We are going add and update database from here.

# Generic Repository Pattern

But I don't want to use sql connection in viewmodels. I want to reduce my work with the database quite a bit here. So let's code our generic repository class for the project. I created interface of the repository:
The folder struct will be like that:
  • +DBManager
    • +Repositories
    • IRepository.cs
    • Repository.cs
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Todo.Business
{
    public interface IRepository<T> where T : class, new()
    {
        Task<List<T>> GetAllAsync();
        Task<T> GetAsync(int id);
        Task<List<T>> GetList<TValue>(Expression<Func<T, bool>> predicate = null, Expression<Func<T, TValue>> orderBy = null);
        Task<T> GetEntity(Expression<Func<T, bool>> predicate);
        AsyncTableQuery<T> AsQueryable();
        Task Add(T entity);
        Task Update(T entity);
        Task Delete(T entity);
    }
}
Before implement of this interface, I need to create connection class for database.

Firstly, We need to install sqlite-net-pcl(1.8.116) from nuget package manager. You don't have install sqlite database engine to your computer. But I think this package is too useful for users of the application. Because they don't have to install any database engine to use application because this is provided by this package. But for testing, you can install DB Browser for SQLite. So you can see what's going on the database. Now we can create DBConnection class:
using Todo.Models;
using SQLite;

namespace Todo.Business
{
    public class DBConnection
    {
        string _path = @"C:\Users\gurbu\Desktop\maui-projects\Todo\DB\Database.db3";

        SQLiteAsyncConnection _connection;
        public SQLiteAsyncConnection Connection { get { return _connection; } }

        public DBConnection()
        {
            _connection = new SQLiteAsyncConnection(_path);

            CreateTables();
        }

        private void CreateTables()
        {
            // tables is created here
        }
    }
}
Let's implement the interface to the repository class:
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Todo.Business
{
    public class Repository<T> : IRepository<T> where T : class, new()
    {
        private SQLiteAsyncConnection _connection;

        public Repository(DBConnection dBConnection)
        {
            _connection = dBConnection.Connection;
        }

        public AsyncTableQuery<T> AsQueryable()
        {
            return _connection.Table<T>();
        }

        public Task<List<T>> GetAllAsync()
        {
            return _connection.Table<T>().ToListAsync();
        }

        public Task<T> GetAsync(int id)
        {
            return _connection.FindAsync<T>(id);
        }

        public Task<T> GetEntity(Expression<Func<T, bool>> predicate)
        {
            return _connection.FindAsync(predicate);
        }

        public async Task<List<T>> GetList<TValue>(Expression<Func<T, bool>> predicate = null, Expression<Func<T, TValue>> orderBy = null)
        {
            AsyncTableQuery<T> table = _connection.Table<T>();

            if(predicate != null)
            {
                table = table.Where(predicate);
            }
            if(orderBy != null)
            {
                table = table.OrderBy<TValue>(orderBy);
            }

            return await table.ToListAsync();

        }

        public async Task Add(T entity)
        {
            await _connection.InsertAsync(entity);
        }

        public async Task Update(T entity)
        {
            await _connection.UpdateAsync(entity);
        }

        public async Task Delete(T entity)
        {
            await _connection.DeleteAsync(entity);
        }
    }
}
Probably this code causes some errors because of the constructor. We need to make dependency injection for it. Open MauiProgram.cs file from the project, and add DBConnection:
using Todo.Business;
using Todo.Business.Repositories;
using Todo.ViewModels;

namespace Todo;

public static class MauiProgram
{
	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

		builder.Services.AddSingleton<DBConnection>();
		
		return builder.Build();
	}
}
Now we can reach the instance of DBConnection from the constructor. 

Let's create our first model which is called Entity:
using SQLite;

namespace Todo.Models
{
    public class Entity
    {
        [PrimaryKey, AutoIncrement]
        public int Id { get; set; }
        public DateTime CreatedTime { get; set; } = DateTime.Now;
        public DateTime UpdatedTime { get; set; }
    }
}
The entity class is inherited by the sub-classes. We don't have to define Id, CreatedTime, UpdatedTime properties for each class. We can just inherit Entity class for them:
using SQLite;

namespace Todo.Models
{
    public class Todo: Entity
    {
        public bool IsChecked { get; set; }

        [MaxLength(250)]
        public string Text{ get; set; }
    }
}
Let's create specific repository class. But why? Because, we will make dependency injection for that specific repository. So that means less lines of code and maintainable.
using Todo.Business.Repositories;
using Todo.Business;
using Todo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Todo.DBManager.Repositories
{
    public interface ITodoRepository : IRepository<Todo>
    {

    }

    public class TodoRepository : Repository<Todo>, ITodoRepository
    {
        private readonly DBConnection _connection;
        public TodoRepository(DBConnection dBConnection) :
            base(dBConnection)
        {
            _connection = dBConnection;
        }
    }
}
Let's go to the MauiProgram.cs file and add these of lines code like below:
		builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
		builder.Services.AddScoped<ITodoRepository, TodoRepository>();
Now we can reach that the instance of the repository from viewmodels constructor. But not now. Before something do that, do not forget to add the necessary function to create our table in the database:
		...
	public DBConnection()
        {
            _connection = new SQLiteAsyncConnection(_path);

            CreateTables();
        }

        private void CreateTables()
        {
            _connection.CreateTableAsync<Todo>();
        }
        ...
Let's run the project and check the file with extension .db3 is created. If the file is existed in the specified folder, so that's good progress. Let's open it using DB Browser for SQlite program:
SQLite overview

# Viewmodels

Now we can create viewmodel that called MainViewModel.cs, it is optional. I defined with that name because I'm going to use MainPage.xaml. 

We need to install CommunityToolkit.Mvvm(8.0.0) package by Microsoft. This package provides easy usage for MVVM pattern. So we just create properties for attributes that are provided by this package to show in view side.

What should we do in this application?
  • Add to-do
  • List to-dos with checkbox
  • Show loading bar with percentage value if checked.
I created folder called ViewModels, and created new file named MainViewModel.cs in this folder. 
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Todo.DBManager.Repositories;
using Todo.Models;

namespace Todo.ViewModels
{

    [ObservableObject]
    public partial class MainViewModel
    {
        private readonly ITodoRepository todoRepository;

        public MainViewModel(ITodoRepository todoRepository)
        {
            this.todoRepository = todoRepository;
        }

        [ObservableProperty]
        private string text;

        [RelayCommand]
        public void AddTodo()
        {
            Todo todo = new Todo();
            todo.Text = text;
            this.todoRepository.Add(todo);
        }
    }
}
I created MainViewModel as partial class, and give an attribute called ObservableObject. Also defined text property like field and, I gave ObservableProperty attribute. The package we installed makes mvvm operations for us. We can use it command in view side if we give RelayCommand attribute to the function. So what did I the code above? Firstly, I made a dependency injection using TodoRepository. After that, I'm able to reach the database and make changes on it. I defined a property called text. I will bind it to a textbox or similar component of .NET MAUI in view side, and also I created a method as a command for adding todo the database. I will bind it as a command to the add button in view side.

Open the MauiProgram.cs and also add singletons of MainPage and MainViewModel:
        builder.Services.AddTransient<MainPage>();
        builder.Services.AddTransient<MainViewModel>();

        return builder.Build();
	}
}
After that open App.xaml.cs and make these changes like below:

namespace Todo;

public partial class App : Application
{
	public App(MainPage mainPage)
	{
		InitializeComponent();

		//MainPage = new AppShell();
		MainPage = new NavigationPage(mainPage);
	}
}
Open the MainPage.xaml.cs and assign mainViewModel to BindingContext:
using Todo.Business.Repositories;
using Todo.ViewModels;

namespace Todo;

public partial class MainPage : ContentPage
{

	public MainPage(MainViewModel mainViewModel)
	{
		InitializeComponent();
		BindingContext = mainViewModel;
	}
    ...
Let's code our page view in MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Todo.MainPage"
             xmlns:viewmodel="clr-namespace:Todo.ViewModels"
             x:DataType="viewmodel:MainViewModel">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="StartAndExpand">

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="7*"/>
                    <ColumnDefinition Width="2*"/>
                </Grid.ColumnDefinitions>
                <Entry Grid.Column="0" MaxLength="250" Text="{Binding Text}"></Entry>
                <Button Grid.Column="1" Text="add to-do" Command="{Binding AddTodoCommand}"/>
            </Grid>
            
        </VerticalStackLayout>
        
    </ScrollView>

</ContentPage>
Let's check the result:
Let's test it:

Okay, that's fine. But CreatedTime value is not look good. But it is actually SQLite date format. It's not problem (for me 👴).
So how to list these data in the bottom of the entry, and the button. Firstly I have to define class as ObservableObject. But I think I cannot do it for models. However, I can use DTO(data transfer object) as solution for this problem. I created a folder called DTOs in the project, and also created file named TodoDto:
using CommunityToolkit.Mvvm.ComponentModel;

namespace Todo.DTOs
{
    [ObservableObject]
    public partial class TodoDto
    {
        [ObservableProperty]
        public bool isChecked;

        [ObservableProperty]
        public string text;
    }
}
Open the MainViewModel.cs and create a list method:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Todo.DBManager.Repositories;
using Todo.DTOs;
using Todo.Models;
using System.Collections.ObjectModel;

namespace Todo.ViewModels
{

    [ObservableObject]
    public partial class MainViewModel
    {
        private readonly ITodoRepository todoRepository;

        [ObservableProperty]
        ObservableCollection<TodoDto> todos = new();

        public MainViewModel(ITodoRepository todoRepository)
        {
            this.todoRepository = todoRepository;
            this.ListTodos();
        }

        [ObservableProperty]
        private string text;

        [RelayCommand]
        public void AddTodo()
        {
            ...
        }

        [RelayCommand]
        public void ListTodos()
        {
            todos = new ObservableCollection<TodoDto>(
                todoRepository.GetAllAsync().Result
                .Select(x => new TodoDto { 
                    Text = x.Text, 
                    IsChecked = x.IsChecked 
                }).ToList()
                );
        }
    }
}
Now let's code the list on the MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Todo.MainPage"
             xmlns:viewmodel="clr-namespace:Todo.ViewModels"
             xmlns:dtos="clr-namespace:Todo.DTOs"
             x:DataType="viewmodel:MainViewModel">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="StartAndExpand">

            <Grid>
                ...
            </Grid>

            <ListView ItemsSource="{Binding Todos}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="dtos:TodoDto">
                        <ViewCell>
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition></RowDefinition>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>

                                <CheckBox IsChecked="{Binding IsChecked}"/>

                                <Label Grid.Column="1" Text="{Binding Text}"/>
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>

            </ListView>

        </VerticalStackLayout>

    </ScrollView>
    
</ContentPage>
Let's test it:
DTO usage as ObservableObject
It works. Of course, I'm not sure my solution(dto usage as ObservableObject) is great idea or not bad. Maybe it can be standard but I have no idea if it is. 

Now the big part is done. From here it is entirely up to you. I'm tired, see you.