AvaloniaUI / Avalonia.Samples

Avalonia.Samples aims to provide some minimal samples focusing on a particular issue at a time. This should help getting new users started.
https://www.avaloniaui.net
606 stars 103 forks source link

Fully Cross platform REAL WORLD example with DI, Routing, AppSettings, Corporate design and Deployment #43

Open sandreas opened 1 year ago

sandreas commented 1 year ago

Hey there,

I'm missing a sample for developing a REAL WORLD App with all the bells and whistles you would like to have in a "professional" App (I'm trying to adopt Avalonia for a company app). The Todo and Music Store App examples are great, but still kind of missing a real world scenario using features you probably would like to have in such a project that should also be deployed on mobile platforms. Even AngelSix, who I think you hired to do an Avalonia Series and who is doing a great job on this, is currently still missing some of these topics that are elementary for a non-beginner.

I would provide a sample myself but I'm pretty new to Avalonia and struggling a bit with these topics (not in general, I got it working, but how to do it the RIGHT way)... At least I'm willing to submit a pull request for a sample Todo app incl. tutorial, but I would need your help to cover the topics below (links or short code examples would be awesome).

Topics to cover

I've only fully completed the checked parts and have a lot of questions about the unchecked ones.

Extended Topics (nice to have)

Here are some topics that would be nice to have later, but that I would not integrate in the first place to keep things simple.

Ideas for a sample

I think Camelot is a pretty good example for many of these topics, but unfortunately not fully cross platform (Android, iOS and WASM are missing) and if you would like to create an App for Android or iOS, a dual pane file manager is not the right choice in my opinion.

So I would extend / add the topics to the existing Todo List tutorial. That would make it easier to keep up to date and to follow along. Although Todo List is pretty simple, it could add some features and need routing, use DI, store some settings for an API URL, etc.

sandreas commented 1 year ago

Cross platform TodoApp:

In this tutorial we're going to be creating a more sophisticated cross platform TODO application in Avalonia using the Model-View-ViewModel (MVVM) pattern together with some more advanced principles like Dependency Injection, Routing, AppSettings, Corporate Design and a full deployment on all platforms.

Creating a new cross platform project

dotnet new avalonia.xplat -o TodoApp

Replace ReactiveUI with CommunityToolkit

Dependency Injection / IoC

To use the de facto C# standard DI container, add Microsoft.DependencyInjection.Extensions as dependency.

Then edit the TodoApp/App.xaml.cs to contain the following:

using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using TodoApp.ViewModels;
using TodoApp.Views;

namespace TodoApp;

public partial class App : Application
{
    public override void Initialize()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public override void OnFrameworkInitializationCompleted()
    {
        IServiceProvider services = ConfigureServices();
        var mainViewModel = services.GetRequiredService<MainViewModel>();
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            desktop.MainWindow = new MainWindow
            {
                DataContext = mainViewModel
            };
        }
        else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
        {
            singleViewPlatform.MainView = new MainView
            {
                DataContext = mainViewModel
            };
        }

        base.OnFrameworkInitializationCompleted();
    }

    private static ServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();
        services.AddSingleton<MainViewModel>();

        //services.AddTransient<SecondaryViewModel>();
        return services.BuildServiceProvider();
    }
}

Create first ViewModel

dotnet new avalonia.usercontrol -o Views -n TodoListView  --namespace Todo.Views

Follow along with the old Todo tutorial.

App Design

Probably you would like to have your app a custom design (e.g. icons).

Replacing the app icon

Usually, you would use svg to build icons to make them scalable for every purpose. However, by default on windows App icons are stored in ico format, so if you would like to build a multi sized icon, you could use ImageMagick (the command line tool convert):

# create temporary icons in your relevant sizes
convert icon.svg -scale 16 16.png
convert icon.svg -scale 32 32.png
convert icon.svg -scale 48 48.png
convert icon.svg -scale 64 64.png
convert icon.svg -scale 128 128.png
convert icon.svg -scale 256 256.png

# create multisize icon
# convert 16.png 32.png 48.png 64.png 128.png 256.png icon.ico
convert *.png icon.ico

Using svg images

AvaloniaUI does not support svg rendering by default, but there is a library called Svg.Skia for this.

Building a Release

Here are some command line instructions to build different platforms (incomplete):

# wasm
dotnet publish TodoApp.Browser -c Release -o dist/wasm/

# android pkg
dotnet publish TodoApp.Android -f net7.0-android -c Release -o dist/android/

# linux, windows (x64)
dotnet publish TodoApp.Desktop -f net7.0 -r "win-x64" -c "Release" -o dist/win-x64 
dotnet publish TodoApp.Desktop -f net7.0 -r "linux-x64" -c "Release" -o dist/linux-x64

On windows you might have an annoying commandline window in the background when using specific build flags, e.g. -p:PublishSingleFile=true --self-contained true -p:PublishReadyToRun=true. To remove that, you can use NSubsys:

<Project Sdk="Microsoft.NET.Sdk">
<!-- ... -->
  <PropertyGroup>
    <NSubsysTasksPath Condition="'$(NSubsysTasksPath)' == ''">$(HOME)/.nuget/packages/nsubsys/1.0.0/tool/NSubsys.Tasks.dll</NSubsysTasksPath>
  </PropertyGroup>

  <UsingTask TaskName="NSubsys.Tasks.NSubsys" AssemblyFile="$(NSubsysTasksPath)" />

  <Target Name="CustomAfterBuild" AfterTargets="Build" Condition="$(RuntimeIdentifier.StartsWith('win'))">
    <NSubsys TargetFile="$(OutputPath)$(AssemblyName)$(_NativeExecutableExtension)" />
  </Target>

  <Target Name="CustomAfterPublish" AfterTargets="Publish" Condition="$(RuntimeIdentifier.StartsWith('win'))">
    <NSubsys TargetFile="$(PublishDir)$(AssemblyName)$(_NativeExecutableExtension)" />
  </Target>
</Project>
timunie commented 1 year ago

@sandreas as you want to make the sample fully xplat, I would prefer to wait until Avalonia 11.0 is out. The reason is that 11.0 is much better when it comes to xplat solutions and honestly I don't want to rely on previews as there may be confusing breaking changes.

sandreas commented 1 year ago

@timunie

@sandreas as you want to make the sample fully xplat, I would prefer to wait until Avalonia 11.0 is out.

Sure. I'm using the preview anyway for my app because the timeframe getting ready is not set to a deadline - it's planned as an Open Source project, as soon I've ironed out the little annoying things.

I don't want to rely on previews as there may be confusing breaking changes.

As you wish. I'm going to document the stuff I found out here, if you don't mind, so feel free to take it as a base for a new tutorial and if you would like to get access to my private experimental repository, you just have to ask.

What I achieved so far for my testing TodoApp:

What I did not achieve / try out so far:

There where some other annoying little things, that I'm going to report issues on the main project (not that you are already have enough) - e.g. binding a Command in ItemsControl did not work in any way I know of (Style, Relative binding path, etc.)

timunie commented 1 year ago

There where some other annoying little things, that I'm going to report issues on the main project (not that you are already have enough) - e.g. binding a Command in ItemsControl did not work in any way I know of (Style, Relative binding path, etc.)

Issues should only reported trying latest nightly if possible. Also you can ping me on telegram in our Avalonia chat. I'd like to help you iron out issues you have, as long as I know how to do.

fgperry commented 1 year ago

@sandreas, I stumbled upon this issue and now that Avalonia 11 is out, I was wondering where this example that you are working on may exist. Thanks!

sandreas commented 1 year ago

I stumbled upon this issue and now that Avalonia 11 is out, I was wondering where this example that you are working on may exist. Thanks!

@fgperry There is no documented Example, but currently I am working on upgrading ToneAudioPlayer from Avalonia 11 preview 6 to stable Avalonia 11. The code should already mostly apply to Avalonia 11 stable. I'm also working on a little blog series including development, deployment on all platforms, caveats and possible improvements of ToneAudioPlayer, but as always, my time is just too limited.

I also worked on a bigger customer project with Avalonia, but I had to switch to MAUI (which I really was not happy with - I think MAUI is so much worse), because I could not get working the WebView and SecureStorage at the time. This unfortunately was a K.O.

I think this is one of Avalonia's only weaknesses (if there is ANY), that the default libraries for daily tasks are not that mature. While I did not find implementations / libraries for WebView, SecureStorage, Preferences, Routing and , I released my own libs for the latter.

However, there still is

duongphuhiep commented 2 weeks ago

Hello,

Disclaimer: I'm discovering avalonia as well.

After research and experimenting things, I think that this small post shows the right way to make Big complex Avalonia applications testable and maintainable, and is the right way Dependency Injection should be used in an Avalonia application.

The main keyword in the post is MV-first:

This project is my playground which followed the above "MV-first" principles:

=> I'm confident that this is the right way for Big, complex Avalonia app

(1): Views are testable, there are tools, lib helping us.. but their tests are usually hard to setup, expensive to maintain and to evole => Just consider them as not testable to make things simple.