Event not triggered in MVP Supervising Controller - c#

I have create abstract class for Presenter
public abstract class MvpPresenter<T>
{
public T View { get; set; }
public MvpPresenter()
{
}
public MvpPresenter(T view)
{
View = view;
}
}
and UserPresenter class that inherit from abstract Presenter
public class UserPresenter2 : MvpPresenter<IUserView>
{
private void OnUserSave(object sender, EventArgs e)
{
if (View.ContextData.IsDirty)
{
User user = new User();
User domainUser = DataMapper.Translate(View.ContextData);
new UserServiceStub().SaveUser(domainUser);
}
}
private void OnUserSearch(object sender, SearchEventArgs e)
{
if (string.IsNullOrEmpty(e.SearchCriteria))
{
View.StatusMessage = "User name can not be null";
return;
}
User user = new UserServiceStub().GetUser(e.SearchCriteria);
if (user == null)
{
View.StatusMessage = String.Format(
"There's no user found for user name:{0}", e.SearchCriteria);
return;
}
UserDTO userDTO = DataMapper.Translate(user);
View.ContextData = userDTO;
}
}
code for view:
public abstract class MvpView<TPresenter, TView> : Page
where TPresenter : MvpPresenter<TView>
{
public TPresenter Presenter { get; set; }
public MvpView()
: base()
{
if (!(this is TView))
throw new Exception("MvpView must implement the interface provider as generic TView type");
// Create and initialize presenter
Presenter = Activator.CreateInstance<TPresenter>();
Presenter.View = (TView)((object)this);
}
}
the problem is when user click search, the page didn't response. I know that search event has not registered yet with the View, but I cannot do it in constructor.

Related

How to Populate Combobox Based on User Input Using MVP?

How To Populate a ComboBox Based on User Input Using C# and WinForms?
I have a combobox which I need to populate based on user input (2 different options), while also using the MVP pattern (Model View Presenter). This is in a C# WinForms project.
For example purposes, let's say I have 2 different categories: Fruits and Shoes. If the user chooses Fruits, then my combobox should populate with fruits, if my user chooses Shoes, then my combobox should populate with shoes.
My category data (Fruits and Shoes) are both stored in separate tables in my database.
Currently I have a Model, View, and Presenter for both categories, which works great, but seems very repetitive (especially when dealing with more than 2 categories). Is there an additional piece/design pattern I can implement to cut down on the repetitiveness?
I have provided some example code below, in the following order:
1. Model, View, and Presenter for Fruits category
2. Model, View, and Presenter for Shoes category
3. WinForms Code Behind Page
////////FRUITS MVP////////
//Model
public class FruitsComboBoxModel
{
public List<Fruits> Fruits { get; set; }
}
//View
public interface IFruitsComboBoxModel
{
void ShowFruitsComboBox(FruitsComboBoxModel fruitsComboBoxModel);
}
//Presenter
public class FruitsComboBoxPresenter
{
IFruitsComboBoxView fruitsComboBoxView;
public FruitsComboBoxPresenter(IFruitsComboBoxView view)
{
fruitsComboBoxView = view;
}
public void Init()
{
var model = GetModel();
fruitsComboBoxView.ShowFruitsComboBox(model);
}
private FruitsComboBoxModel GetModel()
{
var dbFruits = GetFruitsFromDataBase(); //fake call to DB for fruits
var fruitsComboBoxModel = new FruitsComboBoxModel
{
Fruits = dbFruits;
}
return fruitsComboBoxModel;
}
}
////////SHOES MVP////////
//Model
public class ShoesComboBoxModel
{
public List<Shoes> Shoes { get; set; }
}
//View
public interface IShoesComboBoxModel
{
void ShowShoesComboBox(ShoesComboBoxModel shoesComboBoxModel);
}
//Presenter
public class ShoesComboBoxPresenter
{
IShoesComboBoxView shoesComboBoxView;
public ShoesComboBoxPresenter(IShoesComboBoxView view)
{
shoesComboBoxView = view;
}
public void Init()
{
var model = GetModel();
shoesComboBoxView.ShowShoesComboBox(model);
}
private ShoesComboBoxModel GetModel()
{
var dbShoes = GetShoesFromDataBase(); //fake call to DB for shoes
var shoesComboBoxModel = new ShoesComboBoxModel
{
Shoes = dbShoes;
}
return shoesComboBoxModel;
}
}
////////Code Behind Page////////
public partial class ExampleForm : Form, IFruitsComboBoxView, IShoesComboBoxView
{
public CategoryType categoryType { get; set; }
FruitsComboBoxPresenter fruitsComboBoxPresenter;
ShoesComboBoxPresenter shoesComboBoxPresenter;
public ExampleForm(CategoryType type)
{
categoryType = type; //user category selection
}
private void ExampleForm_Load(object sender, EventArgs e)
{
if (categoryType == CategoryType.Fruits)
{
fruitsComboBoxPresenter = new FruitsComboBoxPresenter(this);
fruitsComboBoxPresenter.Init();
}
else if (categoryType == CategoryType.Shoes)
{
shoesComboBoxPresenter = new ShoesComboBoxPresenter(this);
shoesComboBoxPresenter.Init();
}
else
{
throw new Exception("Invalid type detected");
}
}
public void ShowFruitsComboBox(FruitsComboBoxModel fruitsComboBoxModel)
{
comboBox.DataSource = fruitsComboBoxModel.Fruits.Select(x => x.Name).ToList();
}
public void ShowShoesComboBox(ShoesComboBoxModel shoesComboBoxModel)
{
comboBox.DataSource = shoesComboBoxModel.Shoes.Select(x => x.Name).ToList();
}
}
I have debated using only one Model, View, and Presenter for the combobox and on my model having a fruits property (list) and having a shoes property (shoes). But this means I will have a lot of if/else logic inside my model, and have to pass down the user selection.
I think you can make use of some interfaces and abstract classes to reduce some of your duplicate code.
This will require some tweaking, but if you have some compile-able code I can take another look.
public interface IComboItem {
string Name {get; set;}
object Value {get; set;}
}
public class Fruit : IComboItem {
//fruit stuff
}
public class Shoe : IComboItem {
//shoe stuff
}
//View
public interface IComboBoxModel
{
void ShowComboBox(List<IComboItem> comboItems);
}
//Presenter
public abstract class ComboBoxPresenter {
IComboBoxView comboBoxView;
public ComboBoxPresenter(IComboBoxView view){
comboBoxView = view;
}
public void Init(){
var model = GetModel();
comboBoxView.ShowComboBox(model);
}
//force implementors to get the model
private abstract List<IComboItem> GetModel();
}
public class FruitsComboBoxPresenter : ComboBoxPresenter
{
private override List<Fruit> GetModel()
{
return GetFruitsFromDataBase(); //fake call to DB for fruits
}
}
public class ShoeComboBoxPresenter : ComboBoxPresenter
{
private override List<Shoe> GetModel()
{
return GetShoesFromDataBase(); //fake call to DB for fruits
}
}
////////Code Behind Page////////
public partial class ExampleForm : Form, IComboBoxView
{
public CategoryType categoryType { get; set; }
IComboBoxPresenter comboBoxPresenter;
public ExampleForm(CategoryType type)
{
categoryType = type; //user category selection
}
private void ExampleForm_Load(object sender, EventArgs e)
{
if (categoryType == CategoryType.Fruits)
{
comboBoxPresenter = new FruitsComboBoxPresenter(this);
}
else if (categoryType == CategoryType.Shoes)
{
comboBoxPresenter = new ShoesComboBoxPresenter(this);
}
else
{
throw new Exception("Invalid type detected");
}
comboBoxPresenter.Init();
}
public void ShowComboBox(List<IComboItem> comboItems)
{
comboBox.DataSource = comboItems.Select(x => x.Name).ToList();
}
}
I will suggest one view for one presenter. If you need use the multi-model write the code into presenter.
Create the Contract class then define the View and Presenter Interface
public class ExampleFormContract
{
public interface IView
{
// Display the item to the view
void DisplayComboBoxItems(IEnumerable<string> items);
// set the presneter
IPresenter Presenter { set; }
}
public interface IPresenter
{
// init form load
void Init();
}
}
Create ExampleFormPresenter and implement the ExampleFormContract.IPresenter
public class ExampleFormPresenter : ExampleFormContract.IPresenter
{
private ExampleFormContract.IView View { get; set; }
private CategoryType Type { get; set; }
public ExampleFormPresenter(ExampleFormContract.IView view, CategoryType type)
{
// set the view and use the Dependency Injection (IoC)
View = view;
View.Presenter = this;
Type = type;
}
public void Init()
{
// you can write your logic code to here.
if (Type == CategoryType.Fruits)
{
var dbFruits = GetFruitsFromDataBase(); //fake call to DB for fruits
var fruitsLists = dbFruits.Select(x => x.Name).ToList();
// update the item to the view
View.DisplayComboBoxItems(fruitsLists);
}
else if (Type == CategoryType.Shoes)
{
var dbShoes = GetShoesFromDataBase(); //fake call to DB for shoes
var shoesLists = dbShoes.Select(x => x.Name).ToList();
// update the item to the view
View.DisplayComboBoxItems(shoesLists);
}
else
{
throw new Exception("Invalid type detected");
}
}
}
Modify the ExampleForm.cs implement ExampleFormContract.IView
public partial class ExampleForm : Form, ExampleFormContract.IView
{
// set the presenter
public ExampleFormContract.IPresenter Presenter { private get; set; }
public ExampleForm()
{
InitializeComponent();
}
private void ExampleForm_Load(object sender, EventArgs e)
{
Presenter.Init();
}
public void DisplayComboBoxItems(IEnumerable<string> items)
{
// update the view
comboBox.DataSource = items;
}
}
Demo
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// create the form
var form = new ExampleForm();
// use the IoC and Inject the CategoryType what you want
var presneter = new ExampleFormPresenter(form, CategoryType.Fruits);
Application.Run(form);
}
This MVP architecture work fine for me into C# winform. I think it will help you.

In Xamarin.Forms, how to notify the changes of the same viewmodel back to the previous page? (can pass to the second page, but not back)

I got two pages, "HomePage", "SettingPage", including the same "MyView" (some Pickers there).
When I click "Go Setting"(or show more settings) Button from Homepage, the values syncs to the setting page. But When I click "Apply" on the setting page, the values did not come back.
I am new in c# and Xamarin and tried to search online and Microsoft docs. But I couldn't find a way to fix this issue.
Also I was following this link: How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms?
and did the same global value in my code.
MyView (ContentView)
public MyView()
{
InitializeComponent();
BindingContext = GlobalVar.MyViewModel;
Setting1.SetBinding(Picker.ItemsSourceProperty, "ObList1");
Setting1.ItemDisplayBinding = new Binding("obj_text");
Setting1.SetBinding(Picker.SelectedItemProperty, "SelectedItem1");
//also other pickers
}
HomePage (including the MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
SettingPage (including the same MyView)
public partial class SettingPage : ContentPage
{
MyViewModel viewModel { get; set; } = GlobalVar.MyViewModel;
public SettingPage ()
{
BindingContext = viewModel;
}
private async void Click_ApplySetting(object sender, EventArgs e)
{
await Navigation.PopAsync(true);
}
//some other method deal with viewModel
}
GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
ViewModel
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
public obj SelectedItem1 { get; set; }
public obj SelectedItem2 { get; set; }
public obj SelectedItem3 { get; set; }
public MyViewModel()
{
ObList1 = new ObservableCollection<obj>();
ObList2 = new ObservableCollection<obj>();
ObList3 = new ObservableCollection<obj>();
}
}
Maybe I should notify the changes on my SettingPage to viewmodel? or do something in the "set" in viewmodel?
The confusing point is that two pages embed the same view using the same viewmodel, but notify the change from Page1 to Page2 only, not Page2 to Page1.
Any ideas, thx in advance.
Solution One:
Using Event can pass value back to Previous Page.
Define Event in SecondPage :
public delegate void EventHandler(string status);
public event EventHandler EventPass;
Invoke Event when Page disappear:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
In FirstPage, when Naviagtion place need to add the Event here:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
Now value will be passed here:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
Solution Two:
Using Properties Dictionary to store easy and small size data in Application, when enter in page will invoke it to get data from which has been stored.
In Second Page Where you want to store data, writing as bellow:
Application.Current.Properties ["value"] = valuedata;
When back to First Page, override OnAppearing method to update UI:
protected override void OnAppearing()
{
base.OnAppearing();
if (Application.Current.Properties.ContainsKey("value"))
{
var ValueGet = Application.Current.Properties ["value"] as DataType;
// do something with other things
}
}
Note: ViewModel if want to dynamic update data , need to use INotifyPropertyChanged .
Sample Implementation:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModelBase suggest implementing ICommand as a Dictionary structure like:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
So all todo in your ViewModel is just inherit the ViewModelBase class and use it:
class LoginViewModel : ViewModelBase
{
string userName;
string password;
public string UserName
{
get {return userName;}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get{return password;}
set
{
password = value;
OnPropertyChanged("Password");
}
}
#endregion
#region ctor
public LoginViewModel()
{
//Add Commands
Commands.Add("Login", new Command(CmdLogin));
}
#endregion
#region UI methods
private void CmdLogin()
{
// do your login jobs here
}
#endregion
}
Solved.
MyViewModel (updated)
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
private obj _selectedItem1 = new obj();
public obj SelectedItem1
{
get { return _selectedItem1; }
//this is the line solved the problem
//but still not understood thoroughly
set { SetProperty(ref _selectedItem1, value); }
}
//same for _selectedItem2 _selectedItem3
}
ps: BaseViewModel codes here (not changed, from template codes)
public class BaseViewModel : INotifyPropertyChanged
{
//some other attributes
//...
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
It seems that by calling SetProperty, OnPropertyChanged will also be revoked.
But still a little bit confusing about why the previous codes go like kind of "one-way" binding.

C# MVVM How to update viewmodel string from the model

I am really new to mvvm and wpf in c# and got stuck at some very basic stuff.In this example I am using Fody.PropertyChanged. I have a basic viewmodel that holds a string called Test which is binded to a textblock.
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public string Test { get; set; }
}
Then,in a separate file and class called Data,I have a simple function that increments an int and converts it to a string.
public class Data
{
public static int i = 0;
public static string IncTest { get; set; }
public static void Inc()
{
i++;
IncTest = i.ToString();
}
}
How do I update the Test variable inside the viewmodel when calling the Inc() function? For example, when clicking a button
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Model();
Data.Inc();
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
Data.Inc();
}
In MVVM, the model does not update the view model, its actually opposite, The view model updates the model properties.
Here is an example.
MODEL:
public class Model
{
public string Test
{
get;
set;
}
}
View Model:
public class ViewModel : INotifyPropertyChanged
{
private Model _model;
public string Test
{
get
{
return _model.Test;
}
set
{
if(string.Equals(value, _model.Test, StringComparison.CurrentCulture))
{
return;
}
_model.Test = value;
OnPropertyChanged();
}
}
public ViewModel(Model model)
{
_model = model;
}
}
Your views will bind to your view models.
UPDATE: In regards to your question
public class SomeClass
{
public static void Main(string [] args)
{
Model model = new Model();
ViewModel viewModel = new ViewModel(model);
//Now setting the viewmodel.Test will update the model property
viewModel.Test = "This is a test";
}
}

Cross-Thread Call using MVP WIndows Forms

I would like to use MVP Design pattern for a WinForm App but i'm facing the problem of calling a View Update from another thread.
Here's my code
MODEL
public class Model : IModel
{
public string Status { get; set; }
public async void LongOperation(IHomeView View)
{
for (int i = 0; i < 1000; i++)
{
View.StatusListView = i.ToString();
}
}
}
PRESENTER
public class HomePresenter
{
IHomeView _IView;
IModel _IModel;
Model _Model = new Model();
public HomePresenter(IHomeView IView)
{
_IView = IView;
}
public async void LaunchLongOperation()
{
await Task.Run(() => _Model.LongOperation(_IView));
}
}
INTERFACE VIEW-PRESENTER
public interface IHomeView
{
string StatusListView { get; set; }
}
INTERFACE PRESENTER-MODEL
public interface IModel
{
string Status { get; set; }
}
FORM:
public partial class frmMain : Form, IHomeView
{
HomePresenter _Presenter;
public frmMain()
{
InitializeComponent();
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void btnAvvia_Click(object sender, EventArgs e)
{
_Presenter.launchLongOperation();
}
}
i would like to update a list view in the Main form during the long operations of the Model class.
Which is the best way to do that?
Try this code without debugging, you'll be surprised about it works!
The quick and dirty way to make it work in debugging mode as well is to add Control.CheckForIllegalCrossThreadCalls = false; into the constructor of your form.
public partial class MainForm : Form, IHomeView
{
HomePresenter _Presenter;
public MainForm()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false; //<-- add this
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void button1_Click(object sender, EventArgs e)
{
_Presenter.LaunchLongOperation();
}
}

Xamarin passing context from OnClick event

I m a beginner in android dev, I m struggling with passing string Clicked_Message from Click event in Recycle Adapter Class to the other activity. Is it a good way to use Intent? If so how can I pass context to click event? Thanks
public class RecyclerAdapter : RecyclerView.Adapter
{
private RecyclerView mRecyclerView;
private List<NotificationClass> mEmails;
public RecyclerAdapter(List<NotificationClass> emails, RecyclerView recyclerView)
{
mEmails = emails;
mRecyclerView = recyclerView;
}
public class MyView : RecyclerView.ViewHolder
{
public View mMainView { get; set; }
public TextView mName { get; set; }
public TextView mSubject { get; set; }
public TextView mMessage { get; set; }
public MyView(View view) : base(view)
{
mMainView = view;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View row = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.row, parent, false);
TextView txtName = row.FindViewById<TextView>(Resource.Id.txtName);
TextView txtSubject = row.FindViewById<TextView>(Resource.Id.txtSubject);
TextView txtMessage = row.FindViewById<TextView>(Resource.Id.txtMessage);
MyView view = new MyView(row) { mName = txtName, mSubject = txtSubject, mMessage = txtMessage };
return view;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyView myHolder = holder as MyView;
int indexPosition = (mEmails.Count - 1) - position;
myHolder.mMainView.Click += mMainView_Click;
myHolder.mName.Text = mEmails[position].Name;
myHolder.mSubject.Text = mEmails[position].Subject;
myHolder.mMessage.Text = mEmails[position].Message;
}
public override int ItemCount
{
get { return mEmails.Count; }
}
public void OnClick(int position)
{
if (ItemClick != null)
ItemClick(this, position);
}
public void mMainView_Click(object sender, EventArgs e,Context context)
{
int position = mRecyclerView.GetChildPosition((View)sender);
int indexPosition = (mEmails.Count - 1) - position;
Console.WriteLine(mEmails[indexPosition].Message);
string Clicked_Message = (mEmails[indexPosition].Message);
var activity2 = new Intent(context, typeof(ContactActivity));
activity2.PutExtra("MyData", Clicked_Message);
context.StartActivity(activity2);
}
}
You don't need to pass a context. Just use an intent and put the information you want to pass as extras into the intent.
In case your adapter needs a context, pass it in through the constructor and store it as a field member.
This is my typical implementation of the RecyclerView.Adapter with a view holder...
public class ContactsAdapter : V7.RecyclerView.Adapter
{
private List<Contact> _contacts;
public event EventHandler ItemClick;
public void OnItemClick(ContactViewHolder holder)
{
if (ItemClick != null)
{
ItemClick(holder, EventArgs.Empty);
}
}
public ContactsAdapter(List<Contact> contacts)
: base()
{
_contacts = contacts;
}
public override void OnBindViewHolder(V7.RecyclerView.ViewHolder holder, int position)
{
var contactHolder = (ContactViewHolder)holder;
contactHolder.BindUI(_contacts[position]);
}
public override V7.RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var view = LayoutInflater.FromContext(parent.Context).Inflate(Resource.Layout.ContactsListItem, parent, false);
return new ContactViewHolder(view)
{
Adapter = this
};
}
public override int ItemCount
{
get
{
return _contacts.Count;
}
}
}
View Holder (typically in the same file as the adapter)
public class ContactViewHolder : V7.RecyclerView.ViewHolder, View.IOnClickListener
{
public TextView ContactNameTextView { get; set; }
public TextView ContactPhoneTextView { get; set; }
public TextView ContactIntialsTextView { get; set; }
public Contact Contact { get; set; }
private WeakReference _adapter;
public ContactsAdapter Adapter
{
get { return (ContactsAdapter)_adapter.Target; }
set { _adapter = new WeakReference(value); }
}
public ContactViewHolder(View view)
: base(view)
{
GetUI(view);
view.SetOnClickListener(this);
}
private void GetUI(View view)
{
ContactNameTextView = view.FindViewById<TextView>(Resource.Id.ContactName);
ContactPhoneTextView = view.FindViewById<TextView>(Resource.Id.ContactPhone);
ContactIntialsTextView = view.FindViewById<TextView>(Resource.Id.ContactInitialsTextView);
}
public void BindUI(Contact contact)
{
Contact = contact;
ContactNameTextView.Text = contact.ContactName;
ContactPhoneTextView.Text = contact.Phone1;
ContactIntialsTextView.Text = contact.Initials;
}
public void OnClick(View v)
{
Adapter.OnItemClick(this);
}
}
This encapsulates the functionality to the view holder. I also give the instance of the adapter to the view holder as a WeakReference. This allows me to call the OnItemClick, passing the instance of the view holder. If you will notice that the view holder also has an instance of the object that it is representing. This means I don't have to worry about the index that was chosen. I already have the object data available to me.
So the implementation in the Activity/Fragment is like this...
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_contacts = Contact.GetAllContacts();
_adapter = new ContactsAdapter(_contacts);
_adapter.ItemClick += ContactSelected;
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.ContactsFragment, container, false);
var layoutManager = new V7.LinearLayoutManager(this.Activity) { Orientation = V7.LinearLayoutManager.Vertical };
_contactsView = view.FindViewById<V7.RecyclerView>(Resource.Id.ContactList);
_contactsView.SetAdapter(_adapter);
_contactsView.HasFixedSize = true;
_contactsView.SetLayoutManager(layoutManager);
return view;
}
private void ContactSelected (object sender, EventArgs e)
{
var holder = (ContactViewHolder)sender;
var detailFragment = new ContactDetailsFragment(holder.Contact);
MainActivity.ShowFragment(detailFragment);
}
I give the Contact to a Fragment, but you could do something similar for an activity using an intent.
Now whether this is the most efficient way of handling a click of a row in a RecyclerView, I don't know. But this implementation has been working for me.

Categories