How can I create a TabbedPage from data brought from a web service without blocking the user interface? - xamarin.forms

I am trying to create a TabbedPage within a MasterDetailPage from data brought from a web service.
The issue with this is that I am blocking the user interface because I have to wait for the task that JSON brings to then iterate it and create the corresponding ViewModels for each ContentPage of the TabbedPage.
My code is something like:
Mi TabbePage:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyProject.Views.MyTabbedPage"
ItemSource={Binding Tabs}>
<TabbedPage.ItemTemplate>
<DataTemplate>
<ContentPage Title={Binding Description}>
...
</ContentPage>
</DataTemplate>
</TabbedPage.ItemTemplate>
</TabbedPage>
My TabbedPageViewModel:
public TabbedPageViewModel()
{
var task = Task.Run(async () => { await LoadThings(); });
Task.WaitAll(task);
Tabs = new ObservableCollection<TabbedPageDetailViewModel>();
foreach (var t in Things)
{
Tabs.Add(new TabbedPageDetailViewModel(t.IdCode, t.Description));
}
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetCustomerThingsAsync(App.User.IdCustomer));
}
My TabbedPageDetailViewModel:
public TabbedPageDetailViewModel(string idCode, string description)
{
IdCode = idCode;
Description = description;
Task task1 = Task.Run(async () => await LoadTask1(idCode));
var task2 = Task.Run(async () => { await LoadTask2(idCode); });
Task.WaitAll(task1, task2);
}
private async Task LoadTask1(string idCode)
{
//await code that brings API data to load controls
}
private async Task LoadTask(string idCode)
{
// await code that brings API data to load controls
}
All this code blocks the user interface while the data is consumed from the API.
How can I implement this in a correct synchronous way which allows my interface to remain reactive?
PS: I can download my collection of Things in the login process of my app but I still get some delay when TabbedPageDetailViewModel is being instantiated.

How about add the activityindicator in the contentPage? For example:
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="App526.TabbedPage1">
<!--Pages can be added as references or inline-->
<ContentPage Title="{Binding des}" >
<ActivityIndicator IsRunning="true" x:Name="page1ActIndictor" />
</ContentPage>
<ContentPage Title="{Binding name}" >
<ActivityIndicator IsRunning="true" x:Name="page2ActIndictor" />
</ContentPage>
<ContentPage Title="{Binding age}" >
<ActivityIndicator IsRunning="true" x:Name="page3ActIndictor" />
</ContentPage>
</TabbedPage>
And in your code behind, use messagingCenter to stop the activityindicator :
public TabbedPage1()
{
InitializeComponent();
MessagingCenter.Subscribe<Object>(this, "dataLoadFinish", (sender) =>
{
page1ActIndictor.IsRunning = false;
page2ActIndictor.IsRunning = false;
page3ActIndictor.IsRunning = false;
});
}
Send the message when loading finished:
public class TabbedPageDetailViewModel
{
//...
public void loadingTask() {
//After finish loading data
MessagingCenter.Send<Object>(this, "dataLoadFinish");
}
//...
}

Related

Xamarin Community Toolkit AsyncCommand not working

I am creating a Xamarin Forms mobile app using VS 2019 latest version. Xamarin Forms and Essentials packages are also updated to latest.
I have following viewmodel, but the LoadHouses() method is not called via
24. LoadHousesCommand = new AsyncCommand(LoadHouses);
Any idea why? I also get "The breakpoint will not currently be hit" warning. Thanks
Edit:
My xaml page as follows,
<?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="HomeInventory.Views.HousesPage"
xmlns:viewmodels="clr-namespace:HomeInventory.ViewModels"
xmlns:models="clr-namespace:Shared.Models;assembly=Shared"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:DataType="viewmodels:HouseViewModel"
>
<ContentPage.BindingContext>
<viewmodels:HouseViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ListView ItemsSource="{Binding Houses}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:House">
<TextCell Text="{Binding Name}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
When I add houses manually as follows in the constructor it works fine,
public HouseViewModel()
{
Houses = new ObservableRangeCollection<House>();
LoadHousesCommand = new AsyncCommand(LoadHouses);
House h1 = new House();
h1.Name = "House 01";
Houses.Add(h1);
House h2 = new House();
h2.Name = "House 02";
Houses.Add(h2);
}
You can bind the Command with a button or something can execute to call the method,
like:
<StackLayout>
<Button Text="Load"
Command="{Binding LoadHouseCommand}"/>
<CollectionView x:Name="col">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
Here is the ViewModel:
public class HouseViewModel
{public ObservableRangeCollection<House> Houses { set; get; }
public IAsyncCommand LoadHouseCommand { set; get; }
public HouseViewModel()
{
Houses = new ObservableRangeCollection<House>();
LoadHouseCommand = new AsyncCommand(async()=> {
House H1 = new House();
H1.Name = "House 1";
Houses.Add(H1);
House H2 = new House();
H2.Name = "House 2";
Houses.Add(H2);
Console.WriteLine("done");
});
}
result:
If you don't want to bind your command to button or anything. You can simply execute your command from your xaml.cs file like below.
From OnAppearing before your page load. It is the best way for web api.
protected override void OnAppearing()
{
base.OnAppearing();
var vm = BindingContext as HouseViewModel;
vm.LoadHousesCommand.Execute(null);
}
Or, You can simply run it inside your xaml.cs constructor
Task.Run(() =>
{
var vm = BindingContext as HouseViewModel;
vm.LoadHousesCommand.Execute(null);
});

An emptyView for loading data and another for when there is no data available

I have a case of using a CarouselView that is displayed based on certain data brought from an API, the point is that I need to see a certain view or at least text while the API data is being downloaded and another one in case That there is no data.
I tried to get to this using RefreshView and EmptyView but I cannot achieve the required behavior, I can make an EmptyView appear immediately the data begins to load since at that moment the ItemSource is null, then when the data reaches the app the Carousel appears , which seems to me quite ugly, the ideal would be to show some view that next to the RefreshView indicator shows that the data is loading and then in case of not bringing any data show a view that of the feedback that API data did not return .
I hope I have made myself understood and I hope someone can give me an idea on how to achieve this behavior.
MyViewModel:
public MyViewModel()
{
IsRefreshing = true;
Things = new ObservableCollection<Things>();
var t = Task.Run(async () =>
{
await LoadThings();
});
Task.WhenAll(t);
IsRefreshing = false;
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetThingsAsync(Id));
}
My IsRefreshing property is linked to the IsRefreshing property in the RefreshView that encompasses my CarouselView
I think you could use two empty view and switch between them when the refreshing status changes, and here is the code:
add two content view in in XAML and set default empty view to LoadingData:
<ContentPage.Resources>
<ContentView x:Key="LoadingData">
<StackLayout>
<Label Text="Loading data..."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="NoDataLoaded">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CarouselView x:Name="carouselView"
EmptyView="{StaticResource LoadingData}">
... ...
and in code, show different empty view accordingly:
public partial class HorizontalPullToRefreshPage : ContentPage
{
AnimalsViewModel viewModel;
public HorizontalPullToRefreshPage()
{
InitializeComponent();
viewModel = new AnimalsViewModel();
this.BindingContext = viewModel;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsRefreshing"))
{
if (viewModel.IsRefreshing && viewModel.Animals.Count==0)
{
carouselView.EmptyView = Resources["LoadingData"];
}
else if (!viewModel.IsRefreshing && viewModel.Animals.Count == 0)
{
carouselView.EmptyView = Resources["NoDataLoaded"];
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
carouselView.ItemsSource = viewModel.Animals;
}
}
then, every time the property IsRefreshing changed, you got a chance to switch the empty view.
Hope it helps.

Xamarin Forms -> Activity Indicator not working if Commands of statements to be executed

Using Visual Studio 2017 Community 15.8.1
This is after going through all options of stackoverflow regarding ActivityIndicator. So though it may be a duplication but nothing is helping me out.
So finally decided to post my workouts and get best help from here.
What I have tried till now:-
1. {Binding IsLoading} + INotifyPropertyChanged + public void RaisePropertyChanged(string propName) + IsLoading = true; concept.
2. ActivityIndicator_Busy.IsVisible = false; (Direct control accessed)
These two approaches were mostly recommended and I went into depth of each since lot of hours in last few weeks. But nothing got crack.
What I achieved?:-
ActivityIndicator_Busy.IsVisible = false; concept is working smooth only when I put return before executing the statements (for testing purpose); statement on Button Clicked event. (Attached Image)
But as soon as I remove the return; On Pressing Button, directly after some pause, the HomePage Opens.
MY Questions:-
1. This is particular with the current scenario how to get the ActivityIndicator run Immediately when user clicks the Welcome Button.
2. Pertaining to same, When app starts there is also a blank white screen coming for few seconds almost 30 seconds which I also I want to show ActivityIndicator. But dont know how to impose that logic at which instance.
My Inputs
My MainPage.xaml File:-
(Edited 06-Sept-2018 09.11 pm)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="page_main_page"
NavigationPage.HasBackButton="False"
NavigationPage.HasNavigationBar="False"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:appNutri"
BindingContext="{x:Reference page_main_page}"
x:Class="appNutri.MainPage">
<ContentPage.Content>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<StackLayout>
<Image x:Name="Image_Welcome"
Source="welcome.png"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
WidthRequest="300"
HeightRequest="300" />
<Button x:Name="Button_Welcome"
Clicked="Button_Welcome_Clicked"
Text="Welcome!"
BackgroundColor="DeepSkyBlue"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
TextColor="White"
HeightRequest="60" />
</StackLayout>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ActivityIndicator
x:Name="ActivityIndicator_Busy"
Color="Black"
IsEnabled="True"
HorizontalOptions="Center"
VerticalOptions="Center"
IsRunning="{Binding Source={x:Reference page_main_page}, Path=IsLoading}"
IsVisible="{Binding Source={x:Reference page_main_page}, Path=IsLoading}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
My MainPage.cs Code:-
(Edited on 06-Sept-2018 09.13 pm)
using appNutri.Model;
using SQLite;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace appNutri
{
public partial class MainPage : Xamarin.Forms.ContentPage, INotifyPropertyChanged
{
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = this;
}
protected async void Button_Welcome_Clicked(object sender, EventArgs e)
{
IsLoading = true;
await Select_Local_User_Information();
IsLoading = false;
}
private async Task Select_Local_User_Information()
{
IsLoading = true;
string where_clause = "";
try
{
Sql_Common.Database_Folder_Path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string Database_Full_Path = Path.Combine(Sql_Common.Database_Folder_Path, Sql_Common.Database_Name);
SQLiteConnection connection = new SQLiteConnection(Database_Full_Path);
//connection.DropTable<User_Master>();
//connection.Delete(connection.Table<User_Master>());
//connection.CreateTable<User_Master>(CreateFlags.ImplicitPK | CreateFlags.AutoIncPK);
connection.CreateTable<User_Master>();
int count = connection.ExecuteScalar<int>("Select count(*) from User_Master");
if (count == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
//IsLoading = false;
//IsBusy = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
Sql_Common.User_Logged = true;
var Local_User_Data = connection.Table<User_Master>().ToList();
User_Master.Logged_User_Details_Container.First_Name = Local_User_Data[0].First_Name;
User_Master.Logged_User_Details_Container.Cell1 = Local_User_Data[0].Cell1;
where_clause = " Upper ( First_Name ) = " + "'" + User_Master.Logged_User_Details_Container.First_Name.ToUpper().Trim() + "'" + " and " +
" Cell1 = " + "'" + User_Master.Logged_User_Details_Container.Cell1.Trim() + "'";
int records = Sql_Common.Get_Number_Of_Rows_Count("User_Master", where_clause);
if (records == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
IsLoading = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
User_Master.User_Master_Table(where_clause, User_Master.Logged_User_Details_Container);
IsLoading = false;
await Navigation.PushAsync(new User_Home_Page());
}
}
connection.Close();
}
catch (SQLiteException ex)
{
string ex_msg = ex.Message;
}
IsLoading = false;
}
}
}
04-Oct-2018
Finally resolved with This Answer
Update 2018-09-10
You think that you have implemented INotifyPropertyChanged by adding INotifyPropertyChanged to your class definition and adding the event
public event PropertyChangedEventHandler PropertyChanged;
along with its event invocator
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Anyway, since ContentPage already implements INotifyPropertyChanged, adding those did not implement INotifyPropertyChanged. ContentPage already defines the event (or rather BindableObjectfrom which ContentPage indirectly inherits). Any object that relies on being informed about property changes in your page will subscribe to the PropertyChanged event of the ancestor and not the PropertyChanged event you defined, hence the ActivityIndicator will not update.
Just remove the event you defined and call OnPropertyChanged instead of RaisePropertyChanged() and you should be fine.
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
OnPropertyChanged();
}
}
Since OnPropertyChanged is declared as
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
you don't have to pass the property name by hand. The compiler will do that for you beacsue of the CallerMemberNameAttribute.
End Update
The XAML extension {Binding IsLoading} binds the ActivityIndicator to the BindingContext of your page. By default the BindingContext is null, hence there is nothing to bind to and all your efforts are to no avail.
With a viewmodel
The preferred solution would be to use a viewmodel and assign it to MainPage.BindingContext, e.g.
var page = new MainPage()
{
BindingContext = new MainPageViewModel()
}
but if you take that road, you should move all of your UI logic to that viewmodel and encapsulate your SQL access and business logic in other classes, to keep the viewmodel clean from resource accesses and business logic. Having the resource accesses and logic in code behind may work for that small example, but is likely to become an unmaintainable mess.
Without a viewmodel
Anyway, you don't have to use a viewmodel to use bindings. You can set the BindingContext for the page (or some children) or use the Source of the BindingExtension
Setting the BindingContext
The BindingContext is passed from any page or view to it's children. You first have to give your page a name with x:Name="Page" (don't have to use Page, anyway, you can't use the class name of your page) and set the BindingContext to that page
<ContentPage ...
x:Name="Page"
BindingContext="{x:Reference Page}"
...>
now binding to IsLoading should work.
Using Source in the Binding
If you want to reference something else than the BindingContext of a view, BindingExtension has a property Source. You have to give a name to your page, too (see above)
<ContentPage ...
x:Name="Page"
...>
and now you can reference this in your binding
<ActivityIndicator
...
IsRunning="{Binding Path=IsLoading, Source={x:Reference Page}}"
IsVisible="{Binding Path=IsLoading, Source={x:Reference Page}}"/>

How to manage(hide) Back Button and Master Page in navigation while Deeplinking?

On HomePage of Button Update Profile, it redirects and working fine. But when I try to go to Update Profile page from any other place like DeepLink, it shows Back Button with a word Master Page. Can anybody please suggest me what I am missing here?
HomePage(Master)
<?xml version="1.0" encoding="UTF-8"?>
<local:MasterDetailPageWithLifecycle xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject;assembly=MyProject"
x:Class="MyProject.HomePage"
OnAppearingCommand="{Binding OnAppearingCommand}"
Title="Master Page">
<MasterDetailPage.Master>
<ContentPage Title="Home page" Icon="hamburger.png">
<ContentPage.Resources>
<ResourceDictionary>
<local:MenuItemDataTemplateSelector x:Key="menuItemDataTemplateSelector" HighlitedTemplate="{StaticResource highlitedTemplate}"
NormalTemplate="{StaticResource normalTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BackgroundColor="{DynamicResource d8Purple}" VerticalOptions="FillAndExpand" Padding="0, 48, 0, 0">
<StackLayout Padding="0, 40, 0, 0" Spacing="0">
<ListView x:Name="listView" Margin="0,9,0,0" VerticalOptions="FillAndExpand" SeparatorVisibility="None"
ItemSelected="OnItemSelected" ItemTemplate="{StaticResource menuItemDataTemplateSelector}" />
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
</local:MasterDetailPageWithLifecycle>
HomePage.cs
public HomePage()
{
InitializeComponent();
BindingContext =_vm = App.Locator.Home;
NavigationPage.SetHasNavigationBar(this, false);
_masterPageItems = new List<MasterPageItem>();
_masterPageItems.Add(new MasterPageItem
{
Title = "Update Profile",
TargetType = nameof(EditProfilePage)
});
listView.ItemsSource = _masterPageItems;
}
public void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item != null)
{
var name = item.TargetType;
if (name == "EditProfilePage")
{
Detail = new MyProjectNavigationPage(new EditProfilePage());
listView.SelectedItem = null;
IsPresented = false;
}
}
}
public class MyProjectNavigationPage : NavigationPage
{
public MyProjectNavigationPage(Page root) : base(root)
{
if (Device.OS == TargetPlatform.iOS)
{
BarTextColor = Colors.d8Grey;
BarBackgroundColor = Color.White;
Title = root.Title;
}
}
}
EditProfile XAML
<?xml version="1.0" encoding="UTF-8"?>
<local:ContentPageWithCustomBackButton
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject;assembly=MyProject"
x:Class="MyProject.EditProfilePage"
OnAppearingCommand="{Binding OnAppearingCommand}"
Title="Update Profile">
<ContentPage.Content>
<Grid RowSpacing="0">
//Design content
</Grid>
</ContentPage.Content>
</local:ContentPageWithCustomBackButton>
EditProfile CS
public EditProfilePage()
{
InitializeComponent();
BindingContext=_editProfileViewModel = App.Locator.EditProfile;
_editProfileViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
EditProfileDeeplink
public override void Navigate(string uri)
{
_navigationService.NavigateTo(nameof(EditProfilePage));
}
It's just because of when your trying to Navigate from HomePage(Masterpage) to EditProfile Page you set EditProfile Page as Master Detaill Page Like,
if (name == "EditProfilePage")
{
Detail = new MyProjectNavigationPage(new EditProfilePage());
listView.SelectedItem = null;
IsPresented = false;
}
but when you Come from other page you Only Navigate to that Page Like,
_navigationService.NavigateTo(nameof(EditProfilePage));
So you have to handle this Navigation by set page as MasterDetail(DetailPage) Like,
App.Current.MainPage = new MenuMaster {Detail = new NavigationPage(new EditProfile())};
With presenters :
Create class for your presenter
public class IosPagePresenter : MvxFormsIosViewPresenter
{
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues?["NavigationCommand"] == "StackClear")
FormsApplication.MainPage = new ContentPage();
base.Show(request);
}
public IosPagePresenter(IUIApplicationDelegate applicationDelegate, UIWindow window, MvxFormsApplication formsApplication) : base(applicationDelegate, window, formsApplication)
{
}
}
Register this presenter in the setup.IOS
protected override IMvxIosViewPresenter CreatePresenter()
{
var presenter = new IosPagePresenter(ApplicationDelegate, Window, FormsApplication);
Mvx.RegisterSingleton<IMvxFormsViewPresenter>(presenter);
return presenter;
}
And call from ViewModel
var bundle = new MvxBundle(new Dictionary<string, string> { { "NavigationCommand", "StackClear" } });
await _navigationService.Navigate<SavedTankViewModel>(bundle);

How to bind data to Multiple listViews in TabbedPage

I have the following:
1) I have a TabbedPage which contain multiple Tabs.
2) Each Tab contains a page which has ListView
3) The data for each of ListView in each Tab base on a single Json result from REST service
Problem:
At where I should call and consume the Json REST web service and how to bind the data to each of the ListView?
say, at Code Behind of TabbedPage.xaml.cs
I start to call the Json Web service
var client = new System.Net.Http.HttpClient();
var response = await client.GetAsync("Http://Rest WebService");
string GeneralJson = await response.Content.ReadAsStringAsync();
GeneralClassforJsonList ObjGeneralList = new GeneralClassForJsonList();
if (GeneralJson != "")
{
ObjGeneralList = JsonConvert.DeserializeObject<GeneralClassforJsonList>(generalList);
}
var Product = ObjectGeneralList.Result.Products
var Service = ObjectGeneralList.Result.Services
I would like to know How to Binding the data to each of the listviews respectively
in product and service pages:
listviewProduct.ItemsSource = Product
listviewService.ItemsSource = Service
Update(1): Approach 1
In TabbedPage, it has 2 or more tab.
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local ="clr-namespace:SembIWIS.View"
x:Class="myApp.MainPage">
<local:ProductPage>
</local:ProductPage>
<local:ServicePage>
</local:ServicePage>
</TabbedPage>
1)
I created a static Class in Model to store Json Result in string
public static class ProductServiceJson
{
public static string StrProductServiceJson;
}
2)
In TabbedPage.xaml.cs (Code Behind)
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
Init();
}
private void Init()
{
//- code here not execute.
//I start to call the REST api
var response = await client.GetAsync("Http://Rest WebService");
string GeneralJson = await response.Content.ReadAsStringAsync();
ProductServiceJson.StrProductServiceJson = GeneralJson;
}
}
3)
public partial class Product : ContentPage
{
public Product()
{
InitializeComponent();
SetUpListData();
}
private void SetUpListData()
{
//-- code
// how come code here will be executed but not code in tabbedPage.xaml.cs?
}
}
Problem:
1) Why code in TabbedPage.xaml.cs not execute but code in Product.xaml.cs (code behind of Product.xaml)?
2) When code excuted in Product.xaml.cs, the StrProductService is empty or null since web service call in TabbedPage.xaml.cs
3) Will code in Service.xaml.cs be executed?
4) What is the order of execution for TabbedPage, I mean which Tab will have the first execution and if there are 6 tabs?
TIA
Here is how you can do it. I didn't test that code, just wrote to give you an idea.
class ObjGeneral
{
public string Name{get;set;}
}
public class MyViewModel
{
public List<ObjGeneral> MyList{get;set;}
}
Then in page code
var myViewModel=new MyViewModel();
//populate your list
myViewModel.MyList=JsonConvert.DeserializeObject<List<ObjGeneral>>(generalList);
BindingContext=myViewModel;
Then in xaml
<ListView x:Name="toggleListView" ItemsSource="{Binding MyList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="lblPlaceName" Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Resources