How to update badge counter in Parent tab page xamarin forms - c#

I have a App that uses tabbed pages, In the xaml of the Parent tab page
I populate all my other tab pages, I have a viewmodel that binds to the Parent tab page and viewmodels for each of the other Tab pages. I have a badge on one of the tabs that has a counter which shows how many messages there are.
I am having trouble updating the counter.
So I have a call to retrieve the amount of unread messages from the database which is populating into the counter on app load. When i Navigate to view the message it updates the database of that the message has been read , I then navigate back to the tabbed page with a popasync , I then pull to refresh which executes the call to get amount of messages read but it not updating the the counter, if i put a break point on the GetCounter method i see it is updating the counter with the right amount but not changing in on the badge.
Hope that makes sense.
If anyone can help i will be very grateful.
Master Tab Page:
<NavigationPage Title="Message" Icon="email.png" plugin:TabBadge.BadgeText="{Binding counter}"
plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgePosition="PositionTopRight"
plugin:TabBadge.BadgeTextColor="Green">
<x:Arguments>
<local:MessagePage BindingContext="{Binding messages}" />
</x:Arguments>
</NavigationPage>
public partial class MasterTabPage : TabbedPage
{
Master_PageViewModel vm;
public MasterTabPage ()
{
InitializeComponent ();
this.BindingContext = vm = new Master_PageViewModel(Navigation);
}
}
Master Tab Page ViewModel:
public class Master_PageViewModel : INotifyPropertyChanged
{
INavigation Navigation;
private int _counter;
public int counter
{
get => _counter;
set
{
_counter = value;
OnPropertyChanged(nameof(counter));
}
}
public MessagePageViewModel messages { get; set; }
public Master_PageViewModel(INavigation navigation)
{
Navigation = navigation;
messages = new MessagePageViewModel(Navigation);
Init();
counter = 0;
}
public async void Init()
{
await GetCounter();
}
public async Task GetCounter()
{
try
{
using (HttpClient client = new HttpClient())
{
List<MessageModel> msg = new List<MessageModel>();
using (HttpResponseMessage response = await client.GetAsync("http://localhost:53665/api/GetMessagesCount/Id=" + 2 + "/" ))
{
if (response.IsSuccessStatusCode)
{
using (HttpContent content = response.Content)
{
var textresponse = await content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<List<MessageModel>>(textresponse);
foreach (var i in json)
{
msg.Add(new MessageModel
{
msgCounter = i.msgCounter,
});
}
counter = msg[0].msgCounter;
}
}
else
{
}
}
}
}
catch (Exception)
{
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Message Tab ViewModel:
public class MessagePageViewModel : BaseViewModel
{
public ICommand MessageDetailsCommand { get; set; }
INavigation Navigation;
private ObservableCollection<MessageModel> _messagesList;
public ObservableCollection<MessageModel> MessagesList
{
get { return _messagesList; }
set
{
if (_messagesList != value)
{
_messagesList = value;
}
}
}
public ICommand ReloadCommand { get; set; }
public ICommand RefreshCommand
{
get
{
return new Command(async () =>
{
await GetMessages();
Master_PageViewModel vm = new Master_PageViewModel(Navigation,multiMediaPickerService);
await vm.GetCounter();
});
}
}
bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
}
}
public MessagePageViewModel(INavigation navigation)
{
ReloadCommand = new Command(async () => await ReloadPage());
Navigation = navigation;
MessageDetailsCommand = new Command(async (object obj) => await MessageDetails(obj));
Initialize();
}
private async void Initialize()
{
await GetMessages();
}
private async Task ReloadPage()
{
await GetMessages();
}
public async Task GetMessages()
{
List<MessageModel> msg = new List<MessageModel>
.........
MessagesList = new ObservableCollection<MessageModel>(msg);
}
private async Task MessageDetails(object obj)
{
var item = (obj as MessageModel);
await Navigation.PushAsync(new MessageDetailsPage(....));
}
}
}
}

This is because you created a new instance of Master_PageViewModel in your RefreshCommand. It is not the parent tabbed page's binding context so the tab's badge won't be updated even though the GetCounter has been triggered.
You have to pass the parent tabbed view model to your MessagePageViewModel like:
public Master_PageViewModel(INavigation navigation)
{
Navigation = navigation;
messages = new MessagePageViewModel(Navigation, this);
Init();
counter = 0;
}
And change your message page view model's constructor:
Master_PageViewModel parentViewModel
public MessagePageViewModel(INavigation navigation, Master_PageViewModel viewModel)
{
ReloadCommand = new Command(async () => await ReloadPage());
Navigation = navigation;
parentViewModel = viewModel;
// ...
}
At last, trigger the method in your refresh command:
public ICommand RefreshCommand
{
get
{
return new Command(async () =>
{
await GetMessages();
await parentViewModel.GetCounter();
});
}
}
Moreover, I noticed that your MessagePageViewModel used the parent tabbed view model's navigation. I don't think this is a good approach as it has its own NavigationPage so that it should utilize its own navigation instead of the parent's.

Related

Xamarin Android ForceDarkHelper What is it?

Periodically, the application begins to update itself. There is a constant call in the logs:
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
[ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4.MainActivity#a894c70
When this happens, if, for example, you open the menu , it closes itself, if something is filled in, it is cleared, the page is updated. There are no timers in the code. I'm testing the app on Xiaomi Redmi. I repeat sometimes it happens sometimes it doesn't. What is it?
I do not know what the problem is, but occasionally, it happens that the application throws the fingerprint to the page. It is intermittent. Sometimes everything works fine. That is, I go through the fingerprint, the next page opens, everything is normal and a second after 5 I am again thrown to the page where you need to enter the fingerprint.
Code for the authorization page:
public authentification()
{
try
{
InitializeComponent();
bool auth = CrossSettings.Current.GetValueOrDefault("authorized", false);
if (auth == false) { CheckAuth(); }
else
{
Application.Current.MainPage = new MasterLk();
}
}
catch { }
}
async void CheckAuth()
{
try
{
var avail = await CrossFingerprint.Current.IsAvailableAsync();
if (!avail)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
var request = new AuthenticationRequestConfiguration("NeedAuth", "-");
var result = await CrossFingerprint.Current.AuthenticateAsync(request);
if (result.Authenticated)
{
CrossSettings.Current.GetValueOrDefault("authorized", true);
Application.Current.MainPage = new MasterLk();
}
else
{
CheckAuth();
}
}
}
catch { }
}
On the page where it throws it there is a ListView with a binding:
public class OrdersViewModel : BaseViewModel
{
private Table oldLoan;
private bool isRefreshing;
private readonly string clientId;
public bool IsRefreshing
{
get
{
return isRefreshing;
}
set
{
isRefreshing = value;
OnPropertyChanged("IsRefreshing");
}
}
public ICommand RefreshCommand { get; set; }
public ObservableCollection<Table> Loans { get; set; }
public void ShowOrHideLoan(Table loan)
{
if (oldLoan == loan)
{
loan.IsExpanded = !loan.IsExpanded;
Reload(loan);
}
else
{
if (oldLoan != null)
{
oldLoan.IsExpanded = false;
Reload(oldLoan);
}
loan.IsExpanded = true;
Reload(loan);
}
oldLoan = loan;
}
private void Reload(Table loan)
{
var index = Loans.IndexOf(loan);
Loans.Remove(loan);
Loans.Insert(index, loan);
}
public async Task LoadDataAsync()
{
IsRefreshing = true;
Loans.Clear();
try
{
var loans = await ConnectAPI.GetOrdersAsync(clientId);
await Task.Delay(1000);
foreach (var item in loans)
{
Loans.Add(item);
}
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
finally
{
oldLoan = null;
IsRefreshing = false;
}
}
public OrdersViewModel(string clientId)
{
IsRefreshing = false;
this.clientId = clientId;
Loans = new ObservableCollection<Table>();
RefreshCommand = new Command(async () =>
{
await LoadDataAsync();
});
Task.Run(async () => await LoadDataAsync());
}
}
That is, whenever the [ForceDarkHelper] updateByCheckExcludeList: pkg: com.companyname.manimobile activity: crc64d14753dcc52b83b4 event appears.MainActivity#a894c70
Throws it to the print page...
and if you stay on this page, it is updated after a while.
MIUI 12 has made an intelligent dark theme. The system itself repaints the applications if they do not support the dark theme. Apparently this service is ForceDarkHelper. And ExcludeList is in the settings a list of applications that cannot be repainted

Listview not populating when using back arrow after typing in search bar

I have a list view with a search bar, i can search a item in the listview and click on the item and navigate to details of that item, but when i click the back arrow i get an System.NullReferenceException on my HttpResponseMessage.
Could someone please advise me as to what i could be doing wrong.
If the search bar is empty it works fine.
ViewModel
private async Task GetProjects(string email)
{
IsBusy = true;
ProjectList = new ObservableCollection<ProjectModel>();
using (HttpClient client = new HttpClient())
{
try
{
using (HttpResponseMessage response = await client.GetAsync("http://example/api/GetProject/email=" + email + "/"))
{
if (response.IsSuccessStatusCode)
{
using (HttpContent content = response.Content)
{
var textresponse = await content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<List<ProjectModel>>(textresponse);
foreach (var t in json)
{
if (t.pjtIsActive == 1)
{
ProjectList.Add(new ProjectModel
{
..............
});
}
}
IsBusy = false;
}
}
else
{
}
}
}
catch (Exception)
{
IsBusy = false;
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>
(async (text) =>
{
if (text.Length >= 1)
{
ProjectList.Clear();
await GetProjects(EmailAddress);
var projectSearch = ProjectList.Where(c => c.pjtName.ToLower().StartsWith(text.ToLower()) || c.ClientName.ToLower().StartsWith(text.ToLower()) || c.ContractorName.ToLower().StartsWith(text.ToLower()) || c.pjtNumber.ToLower().StartsWith(text.ToLower())).ToList();
ProjectList.Clear();
foreach (var item in projectSearch)
ProjectList.Add(item);
}
else
{
GetProjects(EmailAddress);
}
}));
}
}
private ICommand _projectDetailsCommand;
public ICommand ProjectDetailsCommand=> _projectDetailsCommand?? (_projectDetailsCommand= new Command(async (object obj) => {
var item = (obj as ProjectModel);
ProjectModel project = new ProjectModel();
...........
Navigation.PushAsync(new Project_Details(project));
}));
Content Page
protected override void OnAppearing()
{
BindingContext = new Project_View_ViewModel(Navigation);
base.OnAppearing();
}
You should call the binding context in the constructor and refresh the required data in the OnAppearing,
private Project_View_ViewModel bindingv;
public Project_View()
{
try
{
InitializeComponent();
bindingv = new Project_View_ViewModel(Navigation);
BindingContext = bindingv;
}
catch (Exception ex)
{
Logger.Log(ex);
}
}
protected async override void OnAppearing()
{
base.OnAppearing();
try
{
if (bindingv != null)
{
await bindingv.GetProjects();
}
}
catch (Exception ex)
{
Logger.Log(ex);
}
}

Adding Api interface in ViewModel constructor and navigation stops working Prism

I'm using VS 17 for Xamarin Forms. I've set up Prism in my Xamarin.Forms app and I just added a reference to my Api interface (in ViewModel Constructor) and it makes the app stop navigation to the second page. I need to do this in order to pass parameters etc. I followed this guide:
https://blog.qmatteoq.com/prism-for-xamarin-forms-basic-navigation-and-dependency-injection-part-2/
This is what I did to make the navigation stop working:
private readonly IService _Service;
private ObservableCollection<TodoItem> _topSeries;
public ObservableCollection<TodoItem> TopSeries
{
get { return _topSeries; }
set { SetProperty(ref _topSeries, value); }
}
This is the constructor:
public SecondPageViewModel(IService Service, INavigationService navigationService)
{
_Service = Service;
_navigationService = navigationService;
}
So I cant even reach the above viewmodel because of the above code that I added. I tried to put break points on the DelegateCommand (on first ViewModel) but it just stops after InitializeComponent(); and then nothing happens. No error messages! Thanks!
Update:
My Service class that fetches data:
public class Service : IService
{
public List<TodoItem> TodoList { get; private set; }
HttpClient client;
Service()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
}
public async Task<List<TodoItem>> DataAsync()
{
TodoList = new List<TodoItem>();
var uri = new Uri(string.Format(Constants.RestUrl, string.Empty));
try
{
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
TodoList = JsonConvert.DeserializeObject<List<TodoItem>>(content);
Debug.WriteLine(content);
}
}
catch (Exception ex)
{
Debug.WriteLine(#"ERROR {0}", ex.Message);
}
return TodoList;
}
}
This is my App.Xaml.cs
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<View.MainPage, MainPageViewModel>();
containerRegistry.RegisterForNavigation<View.SecondPage, SecondPageViewModel>();
containerRegistry.Register<IService, Service>();
}
My Interface:
public interface IService
{
Task<List<TodoItem>> DataAsync();
}
This is how I navigate (click from listview):
private EventItem _selectedEvent { get; set; }
public EventItem SelectedEvent
{
get { return _selectedEvent; }
set
{
if (_selectedEvent != value)
{
if (Device.RuntimePlatform == Device.iOS)
{
_selectedEvent = null;
}
else
{
_selectedEvent = value;
}
NavigationParameters navParams = new NavigationParameters();
navParams.Add("PassedValue", _todoItem.name);
_navigationService.NavigateAsync("SecondPage", navParams);
}
}
}
Edit:
When I debug without the ApiService code the command is taking me to new new constructor in the new viewmodel. With the code it does not reach the contructor.
According to your code you have declared constructor like this:
Service()
{
// ...
}
You didn't set access modifier, therefore the default one is internal. Here is the definition:
Internal types or members are accessible only within files in the same
assembly.
Most likely you have your Service.cs declared in another Assembly and Prism can't access its constructor.
Your navigation doesn't work because dependency injection fails. To fix it, just change your access modifier to public:
public Service()
{
// ...
}

HttpClient To Get List With Async/Await Operation

I'm trying to get a list from a web api which I've written before. Then I'll use that list in Xamarin.Forms ListView. My code is here:
public static class DataSource
{
public static async Task<Restoran[]> GetRestoransAsync()
{
// ... Use HttpClient.
using (HttpClient client = new HttpClient())
using (HttpResponseMessage response = await client.GetAsync(page))
using (HttpContent content = response.Content)
{
// ... Read the string.
string result = await content.ReadAsStringAsync();
var restorans = JsonConvert.DeserializeObject<Restoran[]>(result);
return restorans;
}
}
}
My ContentPage:
public class MenuPage : ContentPage
{
ListView listView;
List<Restoran> restorans = new List<Restoran>();
async Task LoadRestorans()
{
restorans = (await DataSource.GetRestoransAsync()).ToList();
}
public MenuPage(string masa)
{
var loadData = LoadRestorans();
loadData.Wait();
listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
}
But when I debugged this code, "LoadRestorans()" method has called just before initialization of "restorans" list. I think I don't understand the mentality of async methods. What should I do?
You have two options.
Create the page using an async factory method
public class MenuPage : ContentPage {
ListView listView;
List<Restoran> restorans = new List<Restoran>();
private async Task LoadRestoransAsync() {
restorans = (await DataSource.GetRestoransAsync()).ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement) {
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
public MenuPage(string masa) {
//...
}
public static async Task<MenuPage> CreateMenuPageAsync(string masa) {
var page = new MenuPage(masa);
await page.LoadRestoransAsync();
return pagel
}
}
Then use it like this in other async event handlers
var page = await MenuPage.CreateMenuPageAsync("<masa here>");
OR
do it in the OnAppearing event.
Subscribe to the Appearing event of the page/view
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
}
and call your async code on an actual even handler
private async void Page_Appearing(object sender, EventArgs e) {
//...call async code here
//unsubscribing from the event
this.Appearing -= Page_Appearing;
}
The full class would look something like this
public class MenuPage : ContentPage {
ListView listView;
List<Restoran> restorans = new List<Restoran>();
private async Task LoadRestoransAsync() {
restorans = (await DataSource.GetRestoransAsync()).ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement) {
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
public MenuPage(string masa) {
//...
}
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
}
private async void Page_Appearing(object sender, EventArgs e) {
//...call async code here
await LoadRestoransAsync();
//unsubscribing from the event
this.Appearing -= Page_Appearing;
}
}
What was happening before was that by calling .Wait(), which is a blocking call the class was mixing async and blocking calls (like .Result and .Wait()) which can lead to deadlocks. Hence why you were unable to go past the method when you tried to test it.
It is call before restorans because the constructor MenuPage is called before the initialization of properties.
I would suggest
public class MenuPage : ContentPage
{
ListView listView;
List<Restoran> restorans;
async Task<Restoran[]> LoadRestorans()
{
return await DataSource.GetRestoransAsync();
}
public MenuPage(string masa)
{
this.restorans = LoadRestorans().GetAwaiter().GetResult().ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = this.restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
}
Note: I have not compiled this code, but this should be proper code as per my understanding.

How to pass string from android render page to PCL page?

How to pass string value to android render page to PCL page.
I want to send token of eventArgs.Account.Properties["access_token"] to PCL page.
how can i do ?Please help.
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginRender))]
namespace TestApp.Droid.Renderers
{
public class LoginRender : PageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
// this is a ViewGroup - so should be able to load an AXML file and FindView<>
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator(
clientId: "", // your OAuth2 client id
scope: "user_about_me", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri("https://www.facebook.com/dialog/oauth"), // the auth URL for the service
redirectUrl: new Uri("https://www.facebook.com/connect/login_success.html")); // the redirect URL for the service
auth.Completed += (sender, eventArgs) => {
if (eventArgs.IsAuthenticated)
{
Toast.MakeText(this.Context, eventArgs.Account.Properties["access_token"], ToastLength.Long).Show();
App.SuccessfulLoginAction.Invoke();
App.SaveToken(eventArgs.Account.Properties["access_token"]);
}
else
{
// The user cancelled
}
};
activity.StartActivity(auth.GetUI(activity));
}
}
}
App.cs
public class App
{
static NavigationPage _NavPage;
public static Page GetMainPage()
{
var profilePage = new ProfilePage();
_NavPage = new NavigationPage(profilePage);
return _NavPage;
}
public static bool IsLoggedIn
{
get { return !string.IsNullOrWhiteSpace(_Token); }
}
static string _Token;
public static string Token
{
get { return _Token; }
}
public static void SaveToken(string token)
{
_Token = token;
}
public static Action SuccessfulLoginAction
{
get
{
return new Action(() => {
_NavPage.Navigation.PopModalAsync();
});
}
}
}
above is my App.cs file code. static method can't return token.
ProfilePage.cs in PCL
public class ProfilePage : BaseContentPage
{
public ProfilePage()
{
string tk = App.Token;
var lbltoken = new Label()
{
FontSize = 20,
HorizontalOptions = LayoutOptions.CenterAndExpand,
Text = tk,
};
var stack = new StackLayout
{
VerticalOptions = LayoutOptions.StartAndExpand,
Children = { lbltoken },
};
Content = stack;
}
}
I'm presuming that you have followed this example here: How to login to facebook in Xamarin.Forms
In that case you can use it in your PCL by calling App.Token
If that isn't working create a static property of the field you are using by calling App.SaveToken(eventArgs.Account.Properties["access_token"]);
With the edits you have made it is apparent that you set the value of your Label before the App.Token has a value.
A quick fix here could be to hook in to the Page.Appearing event, like so;
public class ProfilePage : BaseContentPage
{
private Label _lbltoken;
public ProfilePage()
{
Appearing += (object s, EventArgs a) => {
_lbltoken.Text = App.Token;
};
string tk = App.Token;
_lbltoken = new Label()
{
FontSize = 20,
HorizontalOptions = LayoutOptions.CenterAndExpand,
Text = tk,
};
var stack = new StackLayout
{
VerticalOptions = LayoutOptions.StartAndExpand,
Children = { _lbltoken },
};
Content = stack;
}
}
I've made your Label control a private variable so we can easily refer to it from elsewhere, and create a Event-handler for when your ProfilePage appears.
So every time you Page appears, it will set the value of App.Token in your Label.
This should work. However you would probably be better of checking out techniques like MVVM.

Categories