Xamarin is a Microsoft platform for building cross-platform mobile applications using C# and .NET, allowing code sharing between iOS, Android, and Windows platforms.
# Install Visual Studio with Xamarin workload
# Or Visual Studio for Mac
# Install Xamarin through Visual Studio Installer:
# - Mobile development with .NET workload
# - Android SDK, NDK, and Java development tools
# Create new project
# File -> New -> Project -> Mobile -> Cross-Platform -> Xamarin.Forms
# NuGet Package Manager
Install-Package Xamarin.Forms
Install-Package Xamarin.Essentials
// Variables and types
string name = "Xamarin";
int count = 42;
double price = 99.99;
bool isActive = true;
var inferredType = "Hello World"; // string
// Nullable types
string? nullableString = null;
int? nullableInt = null;
// Collections
List<string> items = new List<string> { "Item1", "Item2", "Item3" };
Dictionary<string, object> data = new Dictionary<string, object>
{
{ "name", "John" },
{ "age", 30 }
};
// Arrays
string[] names = { "Alice", "Bob", "Charlie" };
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
// Interface
public interface IDataService
{
Task<List<User>> GetUsersAsync();
Task<User> GetUserAsync(int id);
}
// Class implementation
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
// Constructor
public User(string name, string email)
{
Name = name;
Email = email;
}
// Methods
public string GetDisplayName()
{
return $"{Name} ({Email})";
}
public override string ToString()
{
return GetDisplayName();
}
}
// Inheritance
public class Employee : User
{
public string Department { get; set; }
public decimal Salary { get; set; }
public Employee(string name, string email, string department)
: base(name, email)
{
Department = department;
}
public override string GetDisplayName()
{
return $"{Name} - {Department}";
}
}
// Generic class
public class Repository<T> where T : class
{
private readonly List<T> _items = new List<T>();
public void Add(T item) => _items.Add(item);
public List<T> GetAll() => _items.ToList();
public T GetById(Func<T, bool> predicate) => _items.FirstOrDefault(predicate);
}
// Async method
public async Task<List<User>> GetUsersAsync()
{
using var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync("https://api.example.com/users");
var users = JsonConvert.DeserializeObject<List<User>>(response);
return users;
}
// Calling async methods
public async Task LoadUsersAsync()
{
try
{
var users = await GetUsersAsync();
// Update UI
UserList = users;
}
catch (Exception ex)
{
// Handle error
await DisplayAlert("Error", ex.Message, "OK");
}
}
// Parallel execution
public async Task<(List<User> users, List<Product> products)> LoadDataAsync()
{
var usersTask = GetUsersAsync();
var productsTask = GetProductsAsync();
await Task.WhenAll(usersTask, productsTask);
return (await usersTask, await productsTask);
}
// App.xaml.cs
public partial class App : Application
{
public App()
{
InitializeComponent();
// Set main page
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
// MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.MainPage"
Title="Home">
<StackLayout Padding="20" Spacing="20">
<Label x:Name="WelcomeLabel"
Text="Welcome to Xamarin.Forms!"
FontSize="24"
HorizontalOptions="Center" />
<Entry x:Name="NameEntry"
Placeholder="Enter your name" />
<Button x:Name="SubmitButton"
Text="Submit"
BackgroundColor="Blue"
TextColor="White"
Clicked="OnSubmitClicked" />
<ListView x:Name="ItemsListView"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}"
Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
// MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
}
private async void OnSubmitClicked(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(NameEntry.Text))
{
WelcomeLabel.Text = $"Hello, {NameEntry.Text}!";
NameEntry.Text = "";
}
else
{
await DisplayAlert("Error", "Please enter your name", "OK");
}
}
}
<!-- StackLayout - Sequential arrangement -->
<StackLayout Orientation="Vertical" Spacing="10" Padding="20">
<Label Text="Item 1" />
<Label Text="Item 2" />
<Label Text="Item 3" />
</StackLayout>
<!-- Grid Layout - Table-like arrangement -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="Name:" />
<Entry Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
<Label Grid.Row="1" Grid.Column="0" Text="Description:" />
<Editor Grid.Row="1" Grid.Column="1" Text="{Binding Description}" />
<Button Grid.Row="2" Grid.ColumnSpan="2" Text="Save" />
</Grid>
<!-- AbsoluteLayout - Precise positioning -->
<AbsoluteLayout>
<Label Text="Top Left"
AbsoluteLayout.LayoutBounds="0,0,100,50"
AbsoluteLayout.LayoutFlags="None" />
<Label Text="Center"
AbsoluteLayout.LayoutBounds="0.5,0.5,100,50"
AbsoluteLayout.LayoutFlags="PositionProportional" />
</AbsoluteLayout>
<!-- RelativeLayout - Relative positioning -->
<RelativeLayout>
<Label x:Name="titleLabel"
Text="Title"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=0.5}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height, Factor=0.1}" />
<Entry RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, ElementName=titleLabel, Property=X}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=titleLabel, Property=Y, Constant=50}" />
</RelativeLayout>
<!-- Text Controls -->
<Label Text="Hello World"
FontSize="18"
TextColor="Blue"
HorizontalOptions="Center" />
<Entry Placeholder="Enter text"
Text="{Binding InputText}"
Keyboard="Email" />
<Editor Text="{Binding LongText}"
HeightRequest="100" />
<!-- Button -->
<Button Text="Click Me"
Command="{Binding ClickCommand}"
BackgroundColor="Blue"
TextColor="White"
CornerRadius="5" />
<!-- Image -->
<Image Source="icon.png"
Aspect="AspectFit"
HeightRequest="100" />
<Image Source="{Binding ImageUrl}"
LoadingPlaceholder="loading.png"
ErrorPlaceholder="error.png" />
<!-- Switch and Slider -->
<Switch IsToggled="{Binding IsEnabled}" />
<Slider Minimum="0" Maximum="100" Value="{Binding SliderValue}" />
<Stepper Minimum="0" Maximum="10" Value="{Binding StepperValue}" />
<!-- Progress -->
<ProgressBar Progress="{Binding ProgressValue}" />
<ActivityIndicator IsRunning="{Binding IsLoading}" />
<!-- Picker -->
<Picker Title="Select an option"
ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption}" />
<!-- DatePicker and TimePicker -->
<DatePicker Date="{Binding SelectedDate}" />
<TimePicker Time="{Binding SelectedTime}" />
<!-- ListView -->
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="15,5">
<Label Text="{Binding Title}" FontAttributes="Bold" />
<Label Text="{Binding Subtitle}" FontSize="Small" TextColor="Gray" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- CollectionView (newer alternative to ListView) -->
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding Title}" FontAttributes="Bold" />
<Label Grid.Row="1" Text="{Binding Description}" FontSize="Small" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
OnPropertyChanged(propertyName);
return true;
}
}
// Command implementation
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
}
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T> _canExecute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
public void Execute(object parameter) => _execute((T)parameter);
}
public class UserListViewModel : BaseViewModel
{
private readonly IUserService _userService;
private ObservableCollection<User> _users;
private User _selectedUser;
private bool _isLoading;
private string _searchText;
public UserListViewModel(IUserService userService)
{
_userService = userService;
Users = new ObservableCollection<User>();
LoadUsersCommand = new RelayCommand(async () => await LoadUsersAsync());
SearchCommand = new RelayCommand(async () => await SearchUsersAsync());
RefreshCommand = new RelayCommand(async () => await RefreshUsersAsync());
// Load initial data
Task.Run(async () => await LoadUsersAsync());
}
public ObservableCollection<User> Users
{
get => _users;
set => SetProperty(ref _users, value);
}
public User SelectedUser
{
get => _selectedUser;
set
{
SetProperty(ref _selectedUser, value);
if (value != null)
{
NavigateToUserDetail(value);
}
}
}
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
public string SearchText
{
get => _searchText;
set => SetProperty(ref _searchText, value);
}
public ICommand LoadUsersCommand { get; }
public ICommand SearchCommand { get; }
public ICommand RefreshCommand { get; }
private async Task LoadUsersAsync()
{
try
{
IsLoading = true;
var users = await _userService.GetUsersAsync();
Users.Clear();
foreach (var user in users)
{
Users.Add(user);
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
finally
{
IsLoading = false;
}
}
private async Task SearchUsersAsync()
{
if (string.IsNullOrWhiteSpace(SearchText))
{
await LoadUsersAsync();
return;
}
try
{
IsLoading = true;
var users = await _userService.SearchUsersAsync(SearchText);
Users.Clear();
foreach (var user in users)
{
Users.Add(user);
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
finally
{
IsLoading = false;
}
}
private async Task RefreshUsersAsync()
{
await LoadUsersAsync();
}
private async void NavigateToUserDetail(User user)
{
await Shell.Current.GoToAsync($"userdetail?id={user.Id}");
}
}
<!-- AppShell.xaml -->
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.AppShell">
<!-- Tab Bar -->
<TabBar>
<ShellContent Title="Home"
Icon="home.png"
ContentTemplate="{DataTemplate views:HomePage}" />
<ShellContent Title="Users"
Icon="users.png"
ContentTemplate="{DataTemplate views:UsersPage}" />
<ShellContent Title="Settings"
Icon="settings.png"
ContentTemplate="{DataTemplate views:SettingsPage}" />
</TabBar>
<!-- Flyout Menu -->
<FlyoutItem Title="Dashboard">
<ShellContent ContentTemplate="{DataTemplate views:DashboardPage}" />
</FlyoutItem>
<FlyoutItem Title="Profile">
<ShellContent ContentTemplate="{DataTemplate views:ProfilePage}" />
</FlyoutItem>
</Shell>
// AppShell.xaml.cs
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
// Register routes
Routing.RegisterRoute("userdetail", typeof(UserDetailPage));
Routing.RegisterRoute("edituser", typeof(EditUserPage));
}
}
// Navigation in ViewModels
public class MainViewModel : BaseViewModel
{
private async Task NavigateToUserDetailAsync(int userId)
{
await Shell.Current.GoToAsync($"userdetail?id={userId}");
}
private async Task GoBackAsync()
{
await Shell.Current.GoToAsync("..");
}
private async Task NavigateAndPassDataAsync()
{
var user = new User { Id = 1, Name = "John" };
await Shell.Current.GoToAsync("userdetail", new Dictionary<string, object>
{
{ "User", user }
});
}
}
// Receiving navigation parameters
[QueryProperty(nameof(UserId), "id")]
public partial class UserDetailPage : ContentPage
{
private int _userId;
public int UserId
{
get => _userId;
set
{
_userId = value;
LoadUser(value);
}
}
private async void LoadUser(int userId)
{
var user = await _userService.GetUserAsync(userId);
BindingContext = new UserDetailViewModel(user);
}
}
// Push/Pop navigation
public async Task NavigateToPageAsync()
{
await Navigation.PushAsync(new UserDetailPage());
}
public async Task NavigateBackAsync()
{
await Navigation.PopAsync();
}
// Modal navigation
public async Task ShowModalAsync()
{
await Navigation.PushModalAsync(new EditUserPage());
}
public async Task CloseModalAsync()
{
await Navigation.PopModalAsync();
}
// Navigation with data
public async Task NavigateWithDataAsync(User user)
{
var detailPage = new UserDetailPage();
detailPage.BindingContext = new UserDetailViewModel(user);
await Navigation.PushAsync(detailPage);
}
public class ApiService
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://api.example.com";
public ApiService()
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
public async Task<List<User>> GetUsersAsync()
{
var response = await _httpClient.GetStringAsync($"{_baseUrl}/users");
return JsonConvert.DeserializeObject<List<User>>(response);
}
public async Task<User> GetUserAsync(int id)
{
var response = await _httpClient.GetStringAsync($"{_baseUrl}/users/{id}");
return JsonConvert.DeserializeObject<User>(response);
}
public async Task<User> CreateUserAsync(User user)
{
var json = JsonConvert.SerializeObject(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{_baseUrl}/users", content);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<User>(responseJson);
}
public async Task<bool> UpdateUserAsync(int id, User user)
{
var json = JsonConvert.SerializeObject(user);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync($"{_baseUrl}/users/{id}", content);
return response.IsSuccessStatusCode;
}
public async Task<bool> DeleteUserAsync(int id)
{
var response = await _httpClient.DeleteAsync($"{_baseUrl}/users/{id}");
return response.IsSuccessStatusCode;
}
}
// Install SQLite NuGet package: sqlite-net-pcl
// Data model
public class User
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(200)]
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}
// Database service
public class DatabaseService
{
private SQLiteAsyncConnection _database;
public DatabaseService(string dbPath)
{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync<User>().Wait();
}
public async Task<List<User>> GetUsersAsync()
{
return await _database.Table<User>().ToListAsync();
}
public async Task<User> GetUserAsync(int id)
{
return await _database.Table<User>()
.Where(u => u.Id == id)
.FirstOrDefaultAsync();
}
public async Task<int> SaveUserAsync(User user)
{
if (user.Id != 0)
{
return await _database.UpdateAsync(user);
}
else
{
return await _database.InsertAsync(user);
}
}
public async Task<int> DeleteUserAsync(User user)
{
return await _database.DeleteAsync(user);
}
}
// Usage in App.xaml.cs
public partial class App : Application
{
static DatabaseService database;
public static DatabaseService Database
{
get
{
if (database == null)
{
database = new DatabaseService(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Users.db3"));
}
return database;
}
}
}
// Save settings
Preferences.Set("username", "john_doe");
Preferences.Set("user_id", 123);
Preferences.Set("is_logged_in", true);
Preferences.Set("theme", "dark");
// Read settings
var username = Preferences.Get("username", "");
var userId = Preferences.Get("user_id", 0);
var isLoggedIn = Preferences.Get("is_logged_in", false);
var theme = Preferences.Get("theme", "light");
// Check if key exists
if (Preferences.ContainsKey("username"))
{
// Key exists
}
// Remove setting
Preferences.Remove("username");
// Clear all settings
Preferences.Clear();
// Save complex objects
var settings = new AppSettings { Theme = "Dark", Notifications = true };
var json = JsonConvert.SerializeObject(settings);
Preferences.Set("app_settings", json);
// Read complex objects
var settingsJson = Preferences.Get("app_settings", "");
if (!string.IsNullOrEmpty(settingsJson))
{
var settings = JsonConvert.DeserializeObject<AppSettings>(settingsJson);
}
// Interface
public interface IFileService
{
Task<string> ReadTextFileAsync(string filename);
Task WriteTextFileAsync(string filename, string content);
string GetLocalFilePath(string filename);
}
// Android implementation (in Android project)
[assembly: Dependency(typeof(FileService))]
namespace MyApp.Droid.Services
{
public class FileService : IFileService
{
public string GetLocalFilePath(string filename)
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
return Path.Combine(path, filename);
}
public async Task<string> ReadTextFileAsync(string filename)
{
string path = GetLocalFilePath(filename);
if (File.Exists(path))
{
return await File.ReadAllTextAsync(path);
}
return string.Empty;
}
public async Task WriteTextFileAsync(string filename, string content)
{
string path = GetLocalFilePath(filename);
await File.WriteAllTextAsync(path, content);
}
}
}
// iOS implementation (in iOS project)
[assembly: Dependency(typeof(FileService))]
namespace MyApp.iOS.Services
{
public class FileService : IFileService
{
public string GetLocalFilePath(string filename)
{
string docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string libFolder = Path.Combine(docFolder, "..", "Library", "Databases");
if (!Directory.Exists(libFolder))
{
Directory.CreateDirectory(libFolder);
}
return Path.Combine(libFolder, filename);
}
public async Task<string> ReadTextFileAsync(string filename)
{
string path = GetLocalFilePath(filename);
if (File.Exists(path))
{
return await File.ReadAllTextAsync(path);
}
return string.Empty;
}
public async Task WriteTextFileAsync(string filename, string content)
{
string path = GetLocalFilePath(filename);
await File.WriteAllTextAsync(path, content);
}
}
}
// Usage in shared code
public class DataManager
{
private readonly IFileService _fileService;
public DataManager()
{
_fileService = DependencyService.Get<IFileService>();
}
public async Task SaveDataAsync(string data)
{
await _fileService.WriteTextFileAsync("data.txt", data);
}
public async Task<string> LoadDataAsync()
{
return await _fileService.ReadTextFileAsync("data.txt");
}
}
<!-- Use OnPlatform for platform-specific values -->
<Label Text="Hello World">
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS" Value="16" />
<On Platform="Android" Value="18" />
<On Platform="UWP" Value="14" />
</OnPlatform>
</Label.FontSize>
</Label>
<!-- Platform-specific styling -->
<Button Text="Click Me">
<Button.BackgroundColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="Blue" />
<On Platform="Android" Value="Green" />
</OnPlatform>
</Button.BackgroundColor>
</Button>
<!-- Device-specific layouts -->
<ContentPage.Content>
<OnPlatform x:TypeArguments="View">
<On Platform="iOS">
<StackLayout BackgroundColor="LightBlue">
<Label Text="iOS Layout" />
</StackLayout>
</On>
<On Platform="Android">
<Grid BackgroundColor="LightGreen">
<Label Text="Android Layout" />
</Grid>
</On>
</OnPlatform>
</ContentPage.Content>
// NUnit test example
[TestFixture]
public class UserServiceTests
{
private UserService _userService;
private Mock<IHttpService> _httpServiceMock;
[SetUp]
public void Setup()
{
_httpServiceMock = new Mock<IHttpService>();
_userService = new UserService(_httpServiceMock.Object);
}
[Test]
public async Task GetUsersAsync_ReturnsUsersList()
{
// Arrange
var expectedUsers = new List<User>
{
new User { Id = 1, Name = "John" },
new User { Id = 2, Name = "Jane" }
};
_httpServiceMock.Setup(x => x.GetAsync<List<User>>("users"))
.ReturnsAsync(expectedUsers);
// Act
var result = await _userService.GetUsersAsync();
// Assert
Assert.AreEqual(expectedUsers.Count, result.Count);
Assert.AreEqual(expectedUsers[0].Name, result[0].Name);
}
[Test]
public void ValidateEmail_ValidEmail_ReturnsTrue()
{
// Arrange
var validEmail = "[email protected]";
// Act
var result = _userService.ValidateEmail(validEmail);
// Assert
Assert.IsTrue(result);
}
}
// ViewModel testing
[TestFixture]
public class UserListViewModelTests
{
private UserListViewModel _viewModel;
private Mock<IUserService> _userServiceMock;
[SetUp]
public void Setup()
{
_userServiceMock = new Mock<IUserService>();
_viewModel = new UserListViewModel(_userServiceMock.Object);
}
[Test]
public async Task LoadUsersCommand_LoadsUsers()
{
// Arrange
var users = new List<User>
{
new User { Id = 1, Name = "John" }
};
_userServiceMock.Setup(x => x.GetUsersAsync())
.ReturnsAsync(users);
// Act
await _viewModel.LoadUsersCommand.ExecuteAsync();
// Assert
Assert.AreEqual(1, _viewModel.Users.Count);
Assert.AreEqual("John", _viewModel.Users[0].Name);
}
}
<!-- Android Manifest permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
// Build configuration
// Right-click Android project -> Properties -> Android Options
// For release build:
// - Set Target Android version
// - Enable ProGuard/R8 for code shrinking
// - Sign with release keystore
// Generate signed APK:
// Build -> Archive -> Distribute -> Ad Hoc
// Info.plist permissions
<key>NSCameraUsageDescription</key>
<string>This app uses camera to take photos</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses location services</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app uses microphone for recording</string>
// Build configuration
// Right-click iOS project -> Properties -> iOS Bundle Signing
// Set provisioning profile and certificate for release
// Enable XAML compilation for better performance
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace MyApp
{
public partial class App : Application
{
// App code
}
}
<!-- Use caching strategy -->
<ListView CachingStrategy="RecycleElement"
HasUnevenRows="False"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!-- Simple cell structure for better performance -->
<StackLayout Orientation="Horizontal" Padding="10">
<Image Source="{Binding Image}" WidthRequest="50" HeightRequest="50" />
<Label Text="{Binding Title}" VerticalOptions="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// Dispose of resources properly
public class MyPage : ContentPage, IDisposable
{
private Timer _timer;
public MyPage()
{
InitializeComponent();
_timer = new Timer(OnTimerCallback, null, 1000, 1000);
}
private void OnTimerCallback(object state)
{
// Timer callback
}
protected override void OnDisappearing()
{
base.OnDisappearing();
Dispose();
}
public void Dispose()
{
_timer?.Dispose();
_timer = null;
}
}
<!-- Essential packages -->
<PackageReference Include="Xamarin.Forms" Version="5.0.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<!-- MVVM -->
<PackageReference Include="Prism.Unity.Forms" Version="8.1.97" />
<PackageReference Include="ReactiveUI.XamForms" Version="13.2.10" />
<!-- HTTP and JSON -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Refit" Version="6.3.2" />
<!-- Database -->
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
<PackageReference Include="SQLiteNetExtensions" Version="2.1.0" />
<!-- UI Components -->
<PackageReference Include="Xamarin.Forms.PancakeView" Version="2.3.0.759" />
<PackageReference Include="CarouselView.FormsPlugin" Version="5.2.0" />
<!-- Images -->
<PackageReference Include="FFImageLoading.Forms" Version="2.4.11.982" />
<!-- Permissions -->
<PackageReference Include="Plugin.Permissions" Version="6.0.1" />
<!-- Analytics and Crash Reporting -->
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="4.4.0" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />