I'm trying to populate a ListView from Another class which run on another thread but I failed to do that.
Here is the main Activity :
public ListView myList;
List < string>contacts = new List< string>();
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Control);
myList = FindViewById<ListView>(Resource.Id.contactsList);
myList.ItemLongClick += MyList_ItemLongClick;
}
public void insertContact(string ContactInfo)
{
myList=FindViewById<ListView>(Resource.Id.contactsList);
contacts.Add(ContactInfo);
ArrayAdapter<string> myArray = new ArrayAdapter<string>(Application.Context, Android.Resource.Layout.SimpleListItem1, contacts);
list.Adapter = null;
list.Adapter = myArray;
myList.DeferNotifyDataSetChanged();
}
public void startAsync(){
Contacts con = new Contacts();
Thread thread = new Thread(()=> con.Start());
thread.Start();
}
I'm firing a method from another class to grap contacts.
There it just get some info and should send it to main Activity
The other class doing like
MainAct main = new MainAct();
main.insertContact("new Contact");
but It did nothing.
I tried to make a method to get the listView from the main activity (like mentioned in some thread) and pass it as second paramter to add contact like :
public ListView getListView()
{
ListView myList = FindViewById<ListView>(Resource.Id.contactsList);
return myList;
}
and after than calling this line which has the ListView :
main.insertContact("new Contact", main.getListView());
but I got an exception :
Java.Lang.NullPointerException: < Timeout exceeded getting exception details>
I tried from the other class to run the line like :
main.RunOnUIThread(()=> main.insertContact("new Contact", main.getListView()));
but got the same exception !
What did I missed ?
NOTE: If I put both classes in one class ( I mean all the code in one class ) It works without a problem with RunOnUIThread(()=> ); . but When I move the method to another class, I got the exception.
EDIT 1:
When I catch the exception I got nullpointer
Exception of type 'Java.Lang.NullPointerException' was thrown.
1) If Contacts is your another class, I suggest you rename it as MyContacts, because there is a Contacts class,it will confused us.
2) You don't need to new MainAct, just pass the context to your another class.
I have tried to reproduce your question, and I add a comment for these codes:
Here is your MyContacts class:
public class MyContacts
{
MainActivity mContext;
public MyContacts(MainActivity context) {
this.mContext = context;
}
public void Start() {
mContext.insertContact("new Contact");
mContext.insertContact("new Contact1");
mContext.insertContact("new Contact2");
}
}
You MainActivity:
public class MainActivity : Activity
{
public ListView myList;
List<string> contacts = new List<string>();
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
myList = FindViewById<ListView>(Resource.Id.contactsList);
//we usually init the data in OnCreate method.
startAsync();
}
public void insertContact(string ContactInfo)
{
//this is redundant, because you have Instantiated the myList in the OnCreate method.
//myList = FindViewById<ListView>(Resource.Id.contactsList);
contacts.Add(ContactInfo);
//You can use this instead of Application.Context.
ArrayAdapter<string> myArray = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, contacts);
myList.Adapter = myArray;
//from your question, I can't find any information about the list property.
//list.Adapter = null;
//list.Adapter = myArray;
//myList.DeferNotifyDataSetChanged();
}
//From your question, I can't find where have you call this method, I will call it in the OnCreate method
public void startAsync()
{
MyContacts con = new MyContacts(this);
Thread thread = new Thread(() => con.Start());
thread.Start();
}
}
Related
I am new to Xamarin and MVVM. In my application I have Tabbed page and I have two child views inside. I am making a network call and getting data from databse in my first child's view model. Data is coming. I wanted to pass some data to my second child(List). I was able to pass data correctly. Inside my second child, I have a button and calling a new Page.
First child's ViewModel...
public async Task getFypDataAsync(string accessToken)
{
fypFullList = await _apiServices.GetFypData(accessToken);
fypFullList.Sort((x, y) => x.rank.CompareTo(y.rank));
fypSendList.AddRange(new List<FypRankData>(fypFullList.GetRange(0, 320)));
new HomeTab2(fypSendList); // send data to second child
}
List's data is available inside my second child's constructor (code behind).
Second child's code behind....
public partial class HomeTab2 : ContentPage
{
bool _istapped = false;
public List<FypRankData> mylist = new List<FypRankData>();
public HomeTab2()
{
InitializeComponent();
BindingContext = new HomeTabVM2();
var vm = BindingContext as HomeTabVM2;
vm.setFypData();
}
public HomeTab2(List<FypRankData> fypSendList)
{
mylist = fypSendList;
Debug.WriteLine(fypSendList.Count.ToString(), " list_yyycount ");
}
private async void Btn1_Clicked(object sender, EventArgs e)
{
if (_istapped)
return;
_istapped = true;
await Navigation.PushAsync(new ChartPage(mylist));
_istapped = false;
}
}
Problem is inside the button click mylist is getting empty. But inside the constructor, value is assigning.
Following this article I created a simple application that displays a list. The list I get from a web service (.asmx web service).
I want to auto update RecyclerView every 5 seconds and I have no idea how to do it.
In WinForms I would have used the Timer component, but I don't know how this works in Xamarin.
MainActivity.cs
[Activity(Label = "CibMonitor", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity : AppCompatActivity
{
RecyclerView recyclerView;
RecyclerView.LayoutManager layoutManager;
ConnectionItemsAdapter adapter;
ConnectionItem[] connectionItems;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
var ci = new ConnectionItem();
//Get list from web service
connectionItems = ci.GetList().ToArray();
//Setup RecyclerView
adapter = new ConnectionItemsAdapter(this, connectionItems);
recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
recyclerView.SetAdapter(adapter);
ChangedData();
}
}
Update
I created new method in activity class as York Shen suggested
void ChangedData()
{
Task.Delay(5000).ContinueWith(t =>
{
var newData = ConnectionItem.GetList();
adapter.RefreshItems(newData);
ChangedData();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This is my update method in adapter class:
public void RefreshItems(List<ConnectionItem> newItems)
{
items.Clear();
items = newItems;
NotifyDataSetChanged();
}
my app stops working, no exception only this message:
You can create a Task which can help you update recyclerView every 5 seconds:
void ChangedData()
{
Task.Delay(5000).ContinueWith(t =>
{
adapter.NotifyDataSetChanged();
ChangedData();//This is for repeate every 5s.
}, TaskScheduler.FromCurrentSynchronizationContext());
}
EDIT:
Sorry for late reply, calling the ChangedData() in Task.Delay() can resolve the repeate problem. You can invoke the ChangedData() in OnCreate() method. Hope this can help you. : )
I'm trying to access global activity variables (which I can't make as static) from a BroadcastReceiver . For that, I create a instance of the activity this way:
class wifiReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
MainActivity activity = (MainActivity)context.ApplicationContext;
...
But i get System.InvalidCastException: Specified cast is not valid. in instance creation line. What am i doing wrong?
EDIT: Some code of my activity
public class MainActivity : Activity
{
private WifiManager _manager;
private List<string> _wifiSignals;
private wifiReceiver _wifiReceiver;
private TextView _Text;
protected override void OnCreate(Bundle bundle)
{
...
_wifiReceiver = new wifiReceiver();
_manager = (WifiManager)GetSystemService(Context.WifiService);
_wifiSignals = new List<string>();
if (_manager.IsWifiEnabled)
{
_manager.StartScan();
}
...
}
And more extensive code from BroadcastReceiver:
public override void OnReceive(Context context, Intent intent)
{
MainActivity activity = (MainActivity)context.ApplicationContext;
activity._wifiSignals.Clear();
activity._wifiSignals.Add("Lista de wifi:\n");
IList<ScanResult> wifiScanList = activity._manager.ScanResults;
foreach (ScanResult wifiNetwork in wifiScanList)
{
activity._wifiSignals.Add(wifiNetwork.Ssid + ": " + wifiNetwork.Level);
}
//activity.presentation(activity._wifiSignals, activity);
activity._manager.StartScan();
}
Although I remember to call MainActivity properties from another activities in previous apps I developed, I'm pretty sure you cant call a function like you try to do with the StartScan().
The option I use normally is to store the data serialized, and call it in Main.
I do a class with some methods like:
class persistence
{
ISharedPreferences prefs;
ISharedPreferencesEditor editor;
public persistence(Context cont)
{
prefs = PreferenceManager.GetDefaultSharedPreferences(cont);
editor = prefs.Edit();
}
public void store(List<articulo> articulos)
{
string raw = JsonConvert.SerializeObject(articulos);
editor.PutString("articulos", raw);
editor.Commit();
}
public List<articulo> recover()
{
string raw = prefs.GetString("articulos", null);
List<articulo> lista;
if (raw == null)
lista = new List<articulo>();
else
lista = JsonConvert.DeserializeObject<List<articulo>>(raw);
return lista;
}
}
In your OnReceive function I call to the store function
In your OnCreate function you can do directly
persistence datos;
protected override void OnCreate(Bundle bundle)
{
_wifiReceiver = new wifiReceiver();
_manager = (WifiManager)GetSystemService(Context.WifiService);
datos = new persistence (this);
_wifiSignals = datos.recover();
if(_wifiSignals.Count>0)
StartScan();
}
This will also keep data from one usage to another, if you don't want just clear the persistence data after call the BroadcastReceiver;
I have a memory leaks issue.
I basically have a MvxTableViewController databound to a observablecollection. I create a MvxTableViewController and add it to my Controller as a child and I add the view as a subviews.
Problem is, the Cell in the table never gets disposed, leaking memory.
// When the view gets removed from the stack, the cells disposed method is never called and I can see the memory not going away, using the Xcode Instruments tool. The table dispose gets called as expected. but not the cell
public partial class ParticipantTableViewCell : MvxTableViewCell
{
readonly UILabel _nameLabel = new UILabel();
readonly UILabel _locationLabel = new UILabel();
readonly PictureContainer _pictureView = new PictureContainer(7.0f);
public ParticipantTableViewCell (IntPtr handle) : base(handle)
{
BackgroundColor = UIColor.Clear;
SelectionStyle = UITableViewCellSelectionStyle.None;
ContentView.AddSubviews(new UIView[] {_nameLabel, _locationLabel, _pictureView});
_pictureView.TransparentBackground = true;
SetupConstraints();
// Since this view is readonly, I've removed the actual binding code
// and instead manually init each views.
this.DelayBind(() => {
_nameLabel.Text = ((ParticipantViewModel)DataContext).Name;
_locationLabel.Text = ((ParticipantViewModel)DataContext).Location;
IsSelected = ((ParticipantViewModel)DataContext).IsSelected;
_pictureView.AvatarImage.Image = UIImage.FromFile ("Media/" + ((ParticipantViewModel)DataContext).AvatarUrl);
});
}
void SetupConstraints ()
{
ContentView.Subviews.ForEach(v => v.TranslatesAutoresizingMaskIntoConstraints = false);
ContentView.AddConstraints(
_pictureView.Height().EqualTo().HeightOf(ContentView),
_pictureView.Width().EqualTo().HeightOf(ContentView),
_pictureView.WithSameCenterY(ContentView),
_pictureView.Left().EqualTo(6.0f).LeftOf(ContentView),
_nameLabel.Left().EqualTo(10.0f).RightOf(_pictureView),
_nameLabel.Right().EqualTo().RightOf(ContentView),
_nameLabel.Bottom().EqualTo(2.0f).CenterYOf(ContentView),
_locationLabel.Left().EqualTo(10.0f).RightOf(_pictureView),
_locationLabel.Right().EqualTo().RightOf(ContentView),
_locationLabel.Top().EqualTo(4.0f).CenterYOf(ContentView)
);
}
// never called
protected override void Dispose (bool disposing)
{
base.Dispose(disposing);
}
}
class FriendsView : MvxTableViewController
{
protected new FriendsViewModel ViewModel { get { return (FriendsViewModel) base.ViewModel; } }
public FriendsView ()
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad();
TableView.BackgroundColor = UIColor.Clear;
TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
TableView.RowHeight = 60;
TableView.ScrollEnabled = false;
var source = new MvxSimpleTableViewSource(TableView, typeof(ParticipantTableViewCell));
TableView.Source = source;
var set = this.CreateBindingSet<FriendsView, FriendsViewModel>();
set.Bind(source).To(vm => vm.YuFitFriends);
set.Apply();
}
}
Anybody got an idea as to why my cells are leaking ?
thanks
pat
**// never called**
protected override void Dispose (bool disposing)
{
base.Dispose(disposing);
}
Do you expect this will be called magically? NO!
Dispose is just a another method in your class which you should call when you don't want its instance anymore.
And also, you should dispose all the disposable instances in your class (eg: PictureContainer) in this Dispose method. Also, make sure you unsubscribe from any event subscriptions. Just calling base.Dispose doesn't release any reference in this class.
I got a working solution... The bugs seems to be with how MvvmCross register cells for reuse. If I derive my own class from MvxBaseTableSource instead of using MvxSimpleTableSource and I create the cells differently, I get everything to dispose properly:
class MySource : MvxTableViewSource
{
public MySource (UITableView tableView) : base(tableView)
{
}
protected override UITableViewCell GetOrCreateCellFor (UITableView tableView, NSIndexPath indexPath, object item)
{
var cell = tableView.DequeueReusableCell (ParticipantTableViewCell.Key) as ParticipantTableViewCell;
if (cell == null)
cell = new ParticipantTableViewCell ();
return cell;
}
}
and in my tableviewcontroller, I use MySource
var source = new MySource(TableView);
Now everything gets collected fine!
I added an event handler to my code and it broke all access to the CollectionViewSources in the SystemHTA class saying "The calling thread cannot access this object because a different thread owns it". My class was working when "this.systemHTA = new SystemHTA();" was placed outside of the DeviceManager_StateChanged() function.
public partial class MainWindow : Window
{
private DeviceManager DeviceManager = DeviceManager.Instance;
public SystemHTA systemHTA;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DeviceManager.StateChanged += new EventHandler<DeviceManagerStateChangedEventArgs>(DeviceManager_StateChanged);
DeviceManager.Initialize();
}
void DeviceManager_StateChanged(object sender, DeviceManagerStateChangedEventArgs e)
{
if (e.State == DeviceManagerState.Operational)
{
this.systemHTA = new SystemHTA();
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.systemHTA.GetViewSourceTest();
}
}
public class SystemHTA
{
private CollectionViewSource _deviceTestSource;
public SystemHTA()
{
_deviceTestSource = new CollectionViewSource();
_deviceTestSource.Source = CreateLoadData<HWController>.ControllerCollection;
}
public void GetViewSourceTest()
{
ListCollectionView view = (ListCollectionView)_deviceTestSource.View; //This creates an error saying a thread already owns _deviceTestSource
}
}
Ok, CollectionViewSource derived classes, BindableList, ObservableCollection etc these classes can only be created in main dispatcher thread only.
However you have to try something of following sort,
Create your collectionviewsource only in your WPF derived classes, use List<> classes to load your objects in different thread and once done, you can transfer from list to collectionviewsource as follow, I would recommend BindingList because you can add multiple items disabling the refresh to remove flickering.
Create your collection object implicitly in your WPF classes as follow
public class MyWindow : UserControl{
BindingList<MyObject> ObjectList = new BindingList<MyObject>;
public MyWindow(){
ObjectList.AllowAdd = true;
ObjectList.AllowDelete = true;
ObjectList.AllowEdit = true;
}
public void LoadObjects(){
ThreadPool.QueryUserItem( (s)=>{
// load your objects in list first in different thread
List<MyObject> list = MyLongMethodToLoadObjects();
Dispatcher.BeginInvoke( (Action)delegate(){
list.RaiseEvents = false;
foreach(MyObject obj in list){
ObjectList.Add(obj);
}
list.RaiseEvents = true;
list.ResetBindings();
});
});
}
}
I dont know this code does not format correctly but you may try seeing it in visual studio to get correct idea.