I'm developing an android only .NET MAUI app and experiencing random crashes when the user taps the screen to create a popup.
The crash happens unpredictably: sometimes when tapping very quickly, sometimes after doing nothing for a minute and then tapping the screen, and not every time. There are instances that tapping is successful for 30+ tries.
Background info: I'm new to MAUI/mobile coding and I'm building an app as a hobby project to track a korfball match. When a goal is scored, the user taps the field image and a popup is created to enter information about the goal.
I'm not sure, but after reading some documentation I think the problem seems related to threading or UI updates, but despite reading and trying several approaches, I couldn't fix it. I suspect a race condition or UI thread issue, but I don't know how to prove or solve it. As you can see in the code below I've tried some stuff out.
Here’s the relevant part of my code, if needed I can share more code:
Relevant part of MainPage.xaml
<Grid>
<!-- Background Image -->
<Image
Aspect="Fill"
HorizontalOptions="FillAndExpand"
Source="background_korfballfield.png"
VerticalOptions="FillAndExpand">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="TouchOnField" />
</Image.GestureRecognizers>
</Image>
Relevant part of MainPage.cs
private async Task UpdateTime()
{
while (_stopwatchService.ElapsedTime != TimeSpan.Zero || _stopwatchService.ElapsedTime.Seconds == 0)
{
MainThread.BeginInvokeOnMainThread(() =>
{
TimerLabel.Text = _stopwatchService.ElapsedTime.ToString(@"mm\:ss");
});
await Task.Delay(200);
}
}
private async void OnStartClicked(object sender, EventArgs e)
{
if (_currentSession.SessionType == SessionType.Vrij_gebruik)
{
await ToastService.CreateToast("Error: Kan niet de stopwatch starten in vrij gebruik!");
return;
}
_stopwatchService.Start();
_ = Task.Run(async () => await UpdateTime());
}
private void OnPauseClicked(object sender, EventArgs e)
{
_stopwatchService.Pause();
}
private void OnResetClicked(object sender, EventArgs e)
{
_stopwatchService.Reset();
TimerLabel.Text = "00:00"; // Reset the timer display
}
#endregion
private async void TouchOnField(object sender, TappedEventArgs e)
{
try
{
if (_currentSession == null)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await ToastService.CreateToast($"Error: Kan niet zonder sessie starten!");
});
return;
}
if (_currentSession.SessionType == SessionType.Vrij_gebruik)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await ToastService.CreateToast($"Error: Kan niet starten als \"vrij gebruik\" sessie!");
});
return;
}
if (!_currentSession.IsActive)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await ToastService.CreateToast($"Error: De sessie is niet actief!");
});
return;
}
var locationInformation = _fieldLocationService.DetermineAttemptLocation(sender, e, _configuration);
if (locationInformation == null)
{
return;
}
var players = await Task.Run(() => _sqliteTeamDatabaseService.GetPlayersByTeamIdAsync(_currentSession.TeamId));
var time = TimerLabel.Text;
var aanvalPlayerNames = players
.Where(player => player.Vak == Vak.Aanval)
.Select(player => player.Name)
.ToList();
var verdedigingPlayerNames = players
.Where(player => player.Vak == Vak.Verdediging)
.Select(player => player.Name)
.ToList();
MainThread.BeginInvokeOnMainThread(() =>
{
var popup = new OnTouchPopup(_currentSession, locationInformation, time, new Tuple<string, string>(ScoreThuis.Text, ScoreUit.Text), players, _sqliteTeamDatabaseService);
foreach (var name in aanvalPlayerNames)
{
popup.AanvalPlayersNameList.Add(name);
}
foreach (var name in verdedigingPlayerNames)
{
popup.VerdedigingPlayersNameList.Add(name);
}
this.ShowPopup(popup);
});
}
catch (Exception ex)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
await ToastService.CreateToast($"Er is een fout opgetreden: {ex.Message}");
});
}
}