I noted something wrong, either it's from me or a bug when experimenting with .Net MAUI.
I have an ObservableCollection property :
public ObservableCollection<LotoModel> Lotteries { get; set; } = new();
and an ObservableProperty (using community mvvm toolkit) :
[ObservableProperty]
public string _lotteriesCount = "A";
When ever I click/touch a Button I load my Lotteries collection. (tested with an hard coded 4 static LotoModel items) :
static GridModel _testGM1 = new GridModel { Name = "TEST Grid #1", Start = 0, End = 10, NumberOfDraw = 2 };
static GridModel _testGM2 = new GridModel { Name = "TEST Grid #2", Start = 0, End = 20, NumberOfDraw = 5 };
static GridModel _testGM3 = new GridModel { Name = "TEST Grid #3", Start = 0, End = 30, NumberOfDraw = 8 };
static GridModel _testGM4 = new GridModel { Name = "TEST Grid #4", Start = 0, End = 50, NumberOfDraw = 10 };
static LotoModel _testLM1 = new LotoModel { Name = "TEST Lottery #1", IsFavorite = true, Grids = new ObservableCollection<GridModel> { _testGM1, _testGM2 } };
static LotoModel _testLM2 = new LotoModel { Name = "TEST Lottery #2", IsFavorite = false, Grids = new ObservableCollection<GridModel> { _testGM3, _testGM4 } };
And the Button command Task :
async Task GetLotteriesAsync()
{
if (IsBusy)
return;
try
{
IsBusy = true;
_buttonCount++;
LotteriesCount = _buttonCount.ToString();
if (Lotteries.Count != 0)
Lotteries.Clear();
Lotteries.Add(_testLM1);
Lotteries.Add(_testLM2);
Lotteries.Add(_testLM1);
Lotteries.Add(_testLM2);
}
catch (Exception e)
{
Log.Error(e, "Error while trying to get our lotteries");
await Application.Current.MainPage.DisplayAlert("Error!", e.Message, "OK");
}
finally
{
IsBusy = false;
}
}
So each time I touch/click my Button, LotteriesCount string property get's updated with a static int counter field value :
static int _buttonCount = 0;
That is OK.
Now I also update this property via this CollectionChangedEventHandler :
public LotteriesVM(LotteryService lotteryService)
{
GetLotteriesCommand = new Command(async () => await GetLotteriesAsync());
Lotteries.CollectionChanged += LotteriesChangedMethod;
}
private void LotteriesChangedMethod(object sender, NotifyCollectionChangedEventArgs e)
{
LotteriesCount = _lotteriesCount + "_" + Lotteries.Count.ToString();
}
And now here the unexpected behavior : The label only update the counter part of it string property, the remaining "_1_2_3_4" added in the handler doesn't get updated in UI.
Note that I'm using a Android Pixel 5 emulator from VS 17.3 preview.
And I also noted that if I force orientation of the emulated android device, then the Label is updated !
Even more, if I force a XAML MinimalWidthRequest="200" for my Label, then it is correctly updated now !
<!-- without MinimumWidthRequest, the label update only after forcing a screen orientation-->
<Label Text="{Binding LotteriesCount}" MinimumWidthRequest="200" HorizontalOptions="Center" FontAttributes="Bold" FontSize="22" TextColor="OrangeRed"/>
So am I doing something wrong or it is a bug ? Thank you.
Related
Hey all I am not sure why I am having such a hard time with this but I am needing to be able to change a tiles isEnabled property depending on if my program detects an internet connection or not.
The WPF code that I am using and needing to change is the following:
<dxlc:TileLayoutControl x:Name="tileLayoutControl1" Margin="150,63,153,57" Padding="5"
AllowAddFlowBreaksDuringItemMoving="False" AllowItemMoving="False" Orientation="Horizontal" ScrollBars="None"
TileClick="tileLayoutControl1_TileClick">
</dxlc:TileLayoutControl>
This gets populated by the code behind on startup:
private void createMenu()
{
List<String> menuIcons = new List<string>();
menuIcons.Add("gamesIcon.png");
menuIcons.Add("movieIcon.png");
menuIcons.Add("musicIcon.png");
menuIcons.Add("televisionIcon.png");
menuIcons.Add("youTubeIcon.png");
menuIcons.Add("androidIcon.png");
foreach (String item in menuIcons)
{
Image finalImage = new Image();
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri("/img/" + item.ToString(), UriKind.Relative);
image.EndInit();
finalImage.Source = image;
image = null;
tileLayoutControl1.Children.Add(new Tile()
{
Content = finalImage,
Name = item.ToString().Replace(".png", ""),
Tag = item.ToString().Replace(".png", "").Replace(".jpg", ""),
Width = 255,
Height = 288,
IsEnabled = false,
Margin = new Thickness(0, 0, 50, 20),
Background = Brushes.Transparent,
BorderThickness = new Thickness(0, 0, 0, 0)
}
}
}
and finally the code that I have checking the internet connection within a timer:
private bool checkInternet()
{
try
{
Ping myPing = new Ping();
PingReply reply = myPing.Send("google.com", 1000, new byte[32], new PingOptions());
return (reply.Status == IPStatus.Success);
}
catch (Exception)
{
return false;
}
}
public firstWindow()
{
InitializeComponent();
var startTimeSpan = TimeSpan.Zero;
var periodTimeSpan = TimeSpan.FromMinutes(5);
var timer = new System.Threading.Timer((e) =>
{
bool hasInternet = checkInternet();
TileLayoutControl test = (TileLayoutControl)this.tileLayoutControl1.FindName("youTubeIcon");
test.IsEnabled = true;
tileLayoutControl1.RegisterName("youTubeIcon", this.isEnabled = true);
}, null, startTimeSpan, periodTimeSpan);
}
I am currently not getting any errors for the lines:
TileLayoutControl test = (TileLayoutControl)this.tileLayoutControl1.FindName("youTubeIcon");
test.IsEnabled = true;
tileLayoutControl1.RegisterName("youTubeIcon", this.isEnabled = true);
But test is null and RegisterName doesn't seem to change the isEnabled property when I try to click on the tile.
I've also read that using Command="{Binding ClickCommand}" may be my answer but that seems to only pertain to MVVM type of patterns which I am not using for this particle program.
I also know that checking the tileLayoutCibtril1 has a Children property that does contain my dynamically created icons but tileLayoutCibtril1.Children does not have a .FindName proeprty.
So what am I missing?
Try to call ApplyTemplate on TileLayoutControl after foreach statement. The template for TileLayoutControl has not yet been applied and therefore FindName returns null. ApplyTemplate builds the current template’s visual tree
foreach (String item in menuIcons)
{
...
}
tileLayoutControl1.ApplyTemplate();
I have a problem. I am using a CollectionView that receives data in a custom ViewModel from my webpage as long as it returns a JSON with the data. Once the Offset in the call >= num_of_rows the webpage prints "Nothing". If that happens I set a boolean HitBottomOfList = true;. Now everytime when it wants to do a webcall it checks if the HitBottomOfList == false.
Full Code
ViewModel:
public class TemplateListViewModel
{
public double WidthHeight { get; set; }
public ICommand LoadTemplates => new Command(LoadTemplateList);
public int CurrentTemplateCountReceived;
public bool HitBottomOfList = false;
public ObservableCollection<TemplateSource> sourceList { get; set; }
public TemplateListViewModel()
{
CurrentTemplateCountReceived = 0;
sourceList = new ObservableCollection<TemplateSource>();
var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
var width = mainDisplayInfo.Width;
var density = mainDisplayInfo.Density;
var ScaledWidth = width / density;
WidthHeight = (ScaledWidth / 2);
loadingTemplates += onLoadingTemplates;
LoadTemplateList();
}
private event EventHandler loadingTemplates = delegate { };
private void LoadTemplateList()
{
loadingTemplates(this, EventArgs.Empty);
}
private async void onLoadingTemplates(object sender, EventArgs args)
{
if (HitBottomOfList == false)
{
List<Template> templateList = await App.RestService.GetTemplates(App.User, CurrentTemplateCountReceived);
if (templateList != null)
{
foreach (var template in templateList)
{
ImageSource source = ImageSource.FromUri(new Uri("mysite.org/myapp/" + template.FileName));
TemplateSource templateSource = new TemplateSource { Id = template.Id, Source = source, WidthHeight = WidthHeight, FileName = template.FileName };
sourceList.Add(templateSource);
}
CurrentTemplateCountReceived = sourceList.Count;
}
else
{
HitBottomOfList = true;
}
}
}
}
The XAML:
<CollectionView ItemsSource="{Binding sourceList}" RemainingItemsThreshold="6"
RemainingItemsThresholdReachedCommand="{Binding LoadTemplates}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<ff:CachedImage
Source="{Binding Source}"
VerticalOptions="Center"
HorizontalOptions="Center"
WidthRequest="{Binding WidthHeight}"
HeightRequest="{Binding WidthHeight}">
<ff:CachedImage.GestureRecognizers>
<TapGestureRecognizer Tapped="imgTemplate_Clicked" />
</ff:CachedImage.GestureRecognizers>
</ff:CachedImage>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
And finally the WebCall that I do:
public async Task<List<Template>> GetTemplates(User user, int offset)
{
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("un", user.Username));
postData.Add(new KeyValuePair<string, string>("pw", user.Password));
postData.Add(new KeyValuePair<string, string>("offset", offset.ToString()));
var content = new FormUrlEncodedContent(postData);
var weburl = "mysite.org/myapp/get_templates.php";
List<Template> response = await PostResponseTemplates(weburl, content);
return response;
}
public async Task<List<Template>> PostResponseTemplates(string weburl, FormUrlEncodedContent content)
{
var response = await client.PostAsync(weburl, content);
var json = await response.Content.ReadAsStringAsync();
if (json != "Nothing")
{
var jObject = JObject.Parse(json);
var templatePropery = jObject["Templates"] as JArray;
List<Template> templateList = new List<Template>();
foreach (var property in templatePropery)
{
List<Template> propertyList = new List<Template>();
propertyList = JsonConvert.DeserializeObject<List<Template>>(property.ToString());
templateList.AddRange(propertyList);
}
var sourcePropery = (JObject)jObject["Source"];
foreach (var property in sourcePropery)
{
string tempplateSource = property.Value.Value<string>();
App.TemplateSource = tempplateSource;
}
return templateList;
}
else
{
ErrorMessage = json;
return default(List<Template>);
}
}
Now the problem is that when it does trigger the RemainingItemsThresholdReachedCommand="{Binding LoadTemplates}"
it executes the command a lot of times after each other, thinking it needs more data, while there is already a command to get new data. This causes the app to get new data with the same offset a few times, so the app will the same data in the CollectionView a lot of times.
I want the app to call the webpage 1 time to receive more images and just let it load, without asking again for new data, so the duplicates in the list will disappear.
So how can I make sure it only asks the data once, when almost hit the bottom?
Update
Using #Jason his code the following is going wrong:
When the code goes through the MyHandler, it fires the LoadTemplateList(); But jumps to the handling = false; before it finished, so the next command is allowed to start, without finishing the other. Any idea how to wait for the method to finish?
use a bool to track if you are already handling the event and ignore any new ones
bool handling = false;
public void MyHandler()
{
// already handling an event, ignore the new one
if (handling) return;
handling = true;
// process event here
handling = false;
}
i just started to work on performance in mobile app development and i am using asynctask. but i encounter a few challenges, whenever i instantiate my recyclerview in the oncreate method of my activity and retrieve data through async task, i then try to update the recyclerview adapter but it doesn't work. Here's my code below. Here's my activity below
public class awarenessActivity : Activity
{
public RecyclerView mRecyclerView;
public RecyclerView.LayoutManager mLayoutManager;
public RecyclerAdapter mAdapter;
public List<AwarePosts> awrPosts;
ScrollView tabsitem;
ImageButton aware;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//preparing the datasource
awrPosts = new List<AwarePosts>();//empty for now, its the data that i'm trying to retrieve with asynctask
//setting view from Layout resource
SetContentView(Resource.Layout.Main);
//get the linearlayout from the layout resource
tabsitem = (ScrollView)FindViewById(Resource.Id.scrollView1);
tabsitem.RemoveAllViews();
LayoutInflater.Inflate(Resource.Layout.awareness, tabsitem);
//this adds this layout to the main page
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
//Create our layoutManager
mLayoutManager = new StaggeredGridLayoutManager(2, 1);
mRecyclerView.SetLayoutManager(mLayoutManager);
mRecyclerView.NestedScrollingEnabled = false;
//instantiate the adapter and pass in it the datasource
mAdapter = new RecyclerAdapter(awrPosts);
mAdapter.ItemClick += OnItemClick;
//plug the adapter into the recycler view
mRecyclerView.SetAdapter(mAdapter);
new retrievePosts(this).Execute();//executing the asynctask
ActionBar.SetHomeButtonEnabled(true);
ActionBar.SetDisplayHomeAsUpEnabled(true);// these are for the back arrow item
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
int id = item.ItemId;
if (id == global::Android.Resource.Id.Home)
{
StartActivity(typeof(menuActivity));
return true;
}
return base.OnOptionsItemSelected(item);
}
public void OnItemClick(object sender, int position)
{
string img, author, caption, info, postid, link, videolink;
try
{
img = awrPosts[position].image;
author = awrPosts[position].author;
caption = awrPosts[position].title;
info = awrPosts[position].externaL_LINK;
postid = awrPosts[position].awarenesS_EVENT_ID.ToString();
link = awrPosts[position].externaL_LINK;
videolink = awrPosts[position].videO_LINK;
string[] arr = new string[] { img.ToString(), author, caption, info, postid, link, videolink };
Bundle b = new Bundle();
Intent awareDetailInt = new Intent(this, typeof(awareDetailActivity));
b.PutStringArray("array", arr);
awareDetailInt.PutExtras(b);
StartActivity(awareDetailInt);
}
catch (Exception ex)
{
Toast.MakeText(this, ex.Message, ToastLength.Long).Show();
}
}
public override void OnBackPressed()
{
Finish();
}
internal List<AwarePosts> getPosts(List<string> imag)
{
List<AwarePosts> ap = new List<AwarePosts>();
try
{
ap.Add(new AwarePosts()
{
awarenesS_EVENT_ID = 1,
gendeR_TARGET = 2,
image = imag.ElementAt(0),
title = "Are you Happy?",
author = "casual optimist",
videO_LINK = "",
externaL_LINK = "",
agE_TARGET_MIN = 5,
agE_TARGET_MAX = 9,
creatioN_DATE = "",
creatioN_USER = ""
});
ap.Add(new AwarePosts()
{
awarenesS_EVENT_ID = 2,
gendeR_TARGET = 2,
image = imag.ElementAt(1),
title = "Positive Vibes",
author = "etsy",
videO_LINK = "",
externaL_LINK = "",
agE_TARGET_MIN = 5,
agE_TARGET_MAX = 9,
creatioN_DATE = "",
creatioN_USER = ""
});
ap.Add(new AwarePosts()
{
awarenesS_EVENT_ID = 3,
gendeR_TARGET = 2,
image = imag.ElementAt(2),
title = "30+ funny comics",
author = "jimmy benton",
videO_LINK = "",
externaL_LINK = "",
agE_TARGET_MIN = 5,
agE_TARGET_MAX = 9,
creatioN_DATE = "",
creatioN_USER = ""
});
ap.Add(new AwarePosts()
{
awarenesS_EVENT_ID = 4,
gendeR_TARGET = 2,
image = imag.ElementAt(3),
title = "Smiling Doodle",
author = "instagram",
videO_LINK = "",
externaL_LINK = "",
agE_TARGET_MIN = 5,
agE_TARGET_MAX = 9,
creatioN_DATE = "",
creatioN_USER = ""
});
ap.Add(new AwarePosts()
{
awarenesS_EVENT_ID = 5,
gendeR_TARGET = 2,
image = imag.ElementAt(4),
title = "Have a good day",
author = "society6",
videO_LINK = "",
externaL_LINK = "",
agE_TARGET_MIN = 5,
agE_TARGET_MAX = 9,
creatioN_DATE = "",
creatioN_USER = ""
});
return ap;
}
catch (WebException web)
{
ap.Add(new AwarePosts { title = web.Response.ToString() });
return ap;
}
catch (Exception ex)
{
ap.Add(new AwarePosts { title = ex.Message + "\n" + ex.StackTrace});
return ap;
}
}
public List<string> drawableImageToBase64String(Context context)//doing some local data collection here
{
List<string> images = new List<string>();
int[] imgs = new int[] { Resource.Drawable.first1, Resource.Drawable.second1, Resource.Drawable.third1, Resource.Drawable.fourth1, Resource.Drawable.fifth2 };
foreach(var item in imgs)
{
Bitmap bitmap = BitmapFactory.DecodeResource(context.Resources, item);
MemoryStream stream = new MemoryStream();
bitmap.Compress(Bitmap.CompressFormat.Png, 100, stream);
byte[] ba = stream.ToArray();
string bal = Convert.ToBase64String(ba);
images.Add(bal);
}
return images;
}
}
here's my edited asynctask code
public class retrievePosts : AsyncTask<Java.Lang.Void, Java.Lang.Void, List<AwarePosts>>
{
awarenessActivity aw;
List<AwarePosts> awr;
Context mContext;
RecyclerAdapter mAdapter;
public retrievePosts(awarenessActivity awre, Context context, RecyclerAdapter adapter)
{
aw = awre;
mContext = context;
mAdapter = adapter;
}
protected override void OnPreExecute()
{
AndroidHUD.AndHUD.Shared.ShowImage(mContext, Resource.Drawable.load2, "Getting Posts...");
}
protected override List<AwarePosts> RunInBackground(Java.Lang.Void[] #params)
{
List<string> img = aw.drawableImageToBase64String(mContext);
awr = aw.getPosts(img);
return awr;
}
protected override void OnPostExecute(List<AwarePosts> result)
{
base.OnPostExecute(result);
mAdapter = new RecyclerAdapter(result);
mAdapter.NotifyDataSetChanged();
AndroidHUD.AndHUD.Shared.Dismiss(mContext);
Toast.MakeText(mContext, "successful", ToastLength.Long).Show();
}
}
and i'm calling it with this
awarenessActivity aw;
ScrollView tabsitem;
ImageButton aware;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//preparing the datasource
aw = new awarenessActivity();
awrPosts = new List<AwarePosts>();
//set our view from the "Playlist" Layout resource
SetContentView(Resource.Layout.Main);
//get the linearlayout from the layout resource
tabsitem = (ScrollView)FindViewById(Resource.Id.scrollView1);
tabsitem.RemoveAllViews();
LayoutInflater.Inflate(Resource.Layout.awareness, tabsitem);
//this adds this layout to the main page
mRecyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
//Create our layoutManager
mLayoutManager = new StaggeredGridLayoutManager(2, 1);
mRecyclerView.SetLayoutManager(mLayoutManager);
mRecyclerView.NestedScrollingEnabled = false;
//instantiate the adapter and pass in it the datasource
mAdapter = new RecyclerAdapter(awrPosts);
mAdapter.ItemClick += OnItemClick;
//plug the adapter into the recycler view
mRecyclerView.SetAdapter(mAdapter);
new retrievePosts(aw, this, mAdapter).Execute();}
You did wrong to new a awarenessActivity in your retrievePosts, you can pass the mAdapter as parameter to your retrievePosts, try to modify your retrievePosts like this:
public class retrievePosts : AsyncTask<string, string, List<AwarePosts>>
{
List<AwarePosts> awr;
Context mContext;
RecyclerAdapter mAdapter
public retrievePosts(Context context, RecyclerAdapter adapter)
{
mContext = context;
mAdapter = adapter;
}
protected override void OnPreExecute()
{
AndroidHUD.AndHUD.Shared.ShowImage(mContext, Resource.Drawable.load2, "Getting Posts...");
}
protected override List<AwarePosts> RunInBackground(params string[] #params)
{
List<string> img = aw.drawableImageToBase64String(mContext);
//not sure what are you doing here, if you want to get some objects from your awarenessActivity, still try to make it as a parameter and pass to here.
return awr;
}
protected override void OnPostExecute(List<AwarePosts> result)// this function is supposed to run on the UI thread
{
base.OnPostExecute(result);
mAdapter = new RecyclerAdapter(result); // assigning the data here
mAdapter.NotifyDataSetChanged();//y'all kn what i'm trying to do here
AndroidHUD.AndHUD.Shared.Dismiss(mContext);
Toast.MakeText(mContext, "successful", ToastLength.Long).Show();
}
}
And when you use it, try like this new retrievePosts(this, mAdapter).Execute();.
I didn't test this code, but it should be something like it. I noticed that in your RunInBackground method, you also tried to get some object from the awarenessActivity, try to pass it like RecyclerAdapter here as parameter to this class. Don't create a new Activity in it.
Finally, i got it to work #grace Feng's answer was correct. i just didn't set the recycler view adapter inside the onpostexecute method after mAdapter.NotifyDataSetChanged();
after adding this line, it worked and my recycler view was populated. Thanks grace feng
mRecyclerView.SetAdapter(mAdapter);
I need to start Another ContentPage before this async method returns value:
public class GettingCountry : ContentPage
{
public static List<string> CountriesList = new List<string>();
MainPage mainPage = new MainPage();
public async Task<List<RootObject>> FetchAsync(string url)
{
string jsonString;
using (var httpClient = new System.Net.Http.HttpClient())
{
var stream = await httpClient.GetStreamAsync(url);
StreamReader reader = new StreamReader(stream);
jsonString = reader.ReadToEnd();
}
var listOfCountries = new List<RootObject>();
var responseCountries = JArray.Parse(JObject.Parse(jsonString)["response"]["items"].ToString());
foreach (var countryInResponse in responseCountries)
{
var rootObject = new RootObject((int)countryInResponse["id"], (string)countryInResponse["title"]);
CountriesList.Add(rootObject.Title);
}
//I NEED TO NAVIGATE TO FillingPage() FROM HERE:
await Navigation.PushAsync(new FillingPage());
//await Navigation.PushModalAsync(new NavigationPage(new FillingPage()));
return listOfCountries;
}
The page that is need to be started is:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FillingPage : ContentPage
{
public FillingPage ()
{
GettingCountry gettingCountry = new GettingCountry();
Label header = new Label
{
Text = "Заполните бланк",
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
TextColor = Color.Blue
};
Entry nameEntry = new Entry()
{
Placeholder = "Имя",
};
Entry surnameEntry = new Entry()
{
Placeholder = "Фамилия"
};
Picker countryPicker = new Picker()
{
Title = "Страна",
VerticalOptions = LayoutOptions.CenterAndExpand
};
foreach (string country in GettingCountry.CountriesList)
{
countryPicker.Items.Add(country);
}
SearchBar townSearchBar = new SearchBar()
{
Placeholder = "Город",
SearchCommand = new Command(() =>
{
})
};
SearchBar universitySearchBar = new SearchBar()
{
Placeholder = "Университет",
SearchCommand = new Command(() =>
{
})
};
Button myButton = new Button()
{
TextColor = Color.Green,
Text = "Выполнить",
FontSize = 22
};
// Accomodate iPhone status bar.
this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 5);
// Build the page.
this.Content = new StackLayout
{
Children =
{
header,
nameEntry,
surnameEntry,
countryPicker,
townSearchBar,
universitySearchBar,
myButton
}
};
}
}
}
But this code await Navigation.PushAsync(new FillingPage()); works well only when I press a button. When I press a button the needed page starts well. But the same code inside a method does not work. I have debagged it. It goes to a FillingPage() but doesn`t launches it when I try to launch it from inside the async method.
This is likely a result of the operation not being performed on the main thread. Try wrapping your code like this:
Device.BeginInvokeOnMainThread(async () =>
{
await Navigation.PushAsync(new FillingPage());
}
Edit: After a private message, I learned that there was not enough information in the question to know the real issue. The application is calling FetchAsync in Application.OnStart and it's not part of the view hiearchy at all so navigation methods would not work. The following was provided:
protected override void OnStart ()
{
getCountry();
}
private async void getCountry()
{
var url = "...";
GettingCountry gettingCountry = new GettingCountry();
await gettingCountry.FetchAsync(url);
}
GettingCountry is a ContentPage being used like some kind of data access class and it's not currently part of the UI as MainPage is set to something else. A quick hack would be something more like:
private async void getCountry()
{
var url = "...";
GettingCountry gettingCountry = new GettingCountry();
var data = await gettingCountry.FetchAsync(url);
await MainPage.Navigation.PushAsync(new FillingPage(data));
}
I would suggest two further areas to look at improving.
Consider refactoring GettingCountry as it does not need to be a ContentPage.
Investigate an alternative calling so that async void is not used.
We're trying to build a Chat App using Xamarin Forms, but we keep getting this annoying bug with the Android keyboard. Whenever the "Send" button is tapped, the focus on the Entry (text box for chatting) is lost and the keyboard disappears. That isn't what we wanted so we added this line to the TapGestureRecognizer:
messageEntry.Focus();
But for some reason, this doesn't happen fast enough, and often the keyboard goes down and immediately up again. This prevents users from quickly posting multiple message sequentially. Does anybody know how this can be fixed?
Thanks to the answer of #AdamKemp in this post, here is my solution. If the touch is within my EntryStackLayout (don't forget to create the empty custom renderer), then I don't dismiss the keyboard (which is what DispatchTouchEvent will do if CurrentFocus is EditText).
public class EditorAndButtonReproPage : ContentPage
{
public EditorAndButtonReproPage()
{
BackgroundColor = Color.Gray;
Padding = 50;
var editor = new Editor {HorizontalOptions = LayoutOptions.FillAndExpand};
var editorButton = new Button {Text = "OK", HorizontalOptions = LayoutOptions.End};
var editorLayout = new EntryStackLayout { Orientation = StackOrientation.Horizontal, Children = { editor, editorButton}, VerticalOptions = LayoutOptions.Start};
var entry = new ExtendedEntry { Placeholder = "Entry", HorizontalOptions = LayoutOptions.FillAndExpand };
var entryButton = new Button { Text = "OK", HorizontalOptions = LayoutOptions.End };
var entryLayout = new EntryStackLayout { Orientation = StackOrientation.Horizontal, Children = { entry, entryButton }, VerticalOptions = LayoutOptions.Start };
Content = new StackLayout {Children = {editorLayout, entryLayout}};
}
}
and in the MainActivity:
private bool _ignoreNewFocus;
public override bool DispatchTouchEvent(MotionEvent e)
{
var currentView = CurrentFocus;
var parent = currentView?.Parent?.Parent;
var entryStackLayout = parent as EntryStackLayout;
if (entryStackLayout != null)
{
var entryLayoutLocation = new int[2];
entryStackLayout.GetLocationOnScreen(entryLayoutLocation);
var x = e.RawX + entryStackLayout.Left - entryLayoutLocation[0];
var y = e.RawY + entryStackLayout.Top - entryLayoutLocation[1];
var entryStackLayoutRect = new Rectangle(entryStackLayout.Left, entryStackLayout.Top, entryStackLayout.Width, entryStackLayout.Height);
_ignoreNewFocus = entryStackLayoutRect.Contains(x, y);
}
var result = base.DispatchTouchEvent(e);
_ignoreNewFocus = false;
return result;
}
public override Android.Views.View CurrentFocus => _ignoreNewFocus ? null : base.CurrentFocus;
It'd be a bit of a hack, but you could spin off an async task that waits 50ms and then invokes the messageEntry.Focus() line on the main UI thread...