I have a little problem, which I can't solve..
Well, I built a BaseActivity.cs Class:
public class BaseActivity<T> : MvxBindingTabActivityView<T> where T : class, IMvxViewModel
{
protected override void OnViewModelSet()
{ }
public override bool OnCreateOptionsMenu(IMenu menu)
{
// GroupId, ItemId, OrderId
menu.Add(0, 0, 0, "Einstellungen").SetIcon(Android.Resource.Drawable.IcMenuManage);
menu.Add(0, 1, 1, "Info").SetIcon(Android.Resource.Drawable.IcMenuInfoDetails);
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
var id = item.ItemId + 1; // (Id is zero-based :)
if (id == 1) // First Item
{
StartActivity(typeof(SettingsShowActivity));
}
else if (id == 2) // Second Item
{
Android.App.AlertDialog.Builder builder = new AlertDialog.Builder(this);
AlertDialog ad = builder.Create();
ad.SetTitle("Information");
ad.SetIcon(Android.Resource.Drawable.IcDialogAlert);
ad.SetMessage("Version: 0.1");
ad.SetButton("OK", (s, e) => { Console.WriteLine("OK Button clicked, alert dismissed"); });
ad.Show();
}
return true;
}
}
The goal of this class is, that I can put things in that I will use in every other Activity, just like here, the OptionsMenu, which is more or less on all Activities..
Then my other two Activities which are inheriting from BaseActivity.cs:
the MainScreenActivity.cs:
[Activity]
public class MainScreenActivity : BaseActivity<MainScreenViewModel>
{
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.MainScreenLayout);
TabHost.TabSpec spec;
Intent intent;
intent = base.CreateIntentFor<AddressesSearchViewModel>();
intent.AddFlags(ActivityFlags.NewTask);
spec = TabHost.NewTabSpec("adressen");
spec.SetIndicator("Adressen");
spec.SetContent(intent);
TabHost.AddTab(spec);
intent = base.CreateIntentFor<ContactsSearchViewModel>();
intent.AddFlags(ActivityFlags.NewTask);
spec = TabHost.NewTabSpec("kontaktpersonen");
spec.SetIndicator("Kontaktpersonen");
spec.SetContent(intent);
TabHost.AddTab(spec);
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
}
}
and the LoginActivity.cs:
[Activity]
public class LoginActivity : BaseActivity<LoginViewModel>
{
protected override void OnResume()
{
base.OnResume();
App.IsLoggedIn = false;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.Login);
//App.MessageHub.Subscribe<ErrorMessage>((m) => { ErrorMessageAlert(m.Message, m.Title); });
}
}
Its compiling fine, but the app crashes when I start it, and thats the errormessage I get: Your content must have a TabHost whose id attribute is 'android.R.id.tabhost' . I suggest, that it is because I "needed" to implement the abstract interface into the BaseActivity.cs :
protected override void OnViewModelSet()
{ }
So maybe he walks into the 'false' OnViewModelSet(), (In the empty one instead of the one which is building the Tabhost).. but I'm actually not sure.. btw this comes from: MvxBindingTabActivityView..
Hmm any help would be appreciated
I think this is a quite simple problem...
MvxBindingTabActivityView inherits from TabActivity (see source) and it's this class that requires the content - Your content must have a TabHost whose id attribute is 'android.R.id.tabhost'
If you don't want to use Tabs, then just inherit from MvxBindingActivityView instead - this is what the conference sample does - https://github.com/slodge/MvvmCross/blob/vnext/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Droid/Views/BaseView.cs
If one of your activities needs to do tabs, but the other doesn't then they need to inherit using different inheritance trees. If you want to share code between the two base classes, then the best way to do this in C# seems to be using extension methods - e.g. see BaseViewExtensionMethods.cs shared between BaseView.cs, BaseTabbedView.cs and BaseMapView.cs in the conference sample.
Related
I've started to learn Xamarin few days ago and now I'm facing a refactoring problem.
Currently I've got multiple screen layout with bottom navigation bar for TABs to switch between screens. Every activity's layout is adding BottomNavigationBarView at the bottom and every activity's .cs initialize NavigationItemSelected inside their OnCreate methods in this way:
var bottomNavigationBar = FindViewById<BottomNavigationView>(Resource.Id.bottomNavigationBar);
bottomNavigationBar.SelectedItemId = Resource.Id.navigationBarMain; //This id is different for every activity
bottomNavigationBar.NavigationItemSelected += (sender, e) =>
{
switch (e.Item.ItemId)
{
case Resource.Id.navigationBarMain:
StartActivity(typeof(MainActivity));
break;
case Resource.Id.navigationBarHistory:
StartActivity(typeof(HistoryActivity));
break;
case Resource.Id.navigationBarSettings:
StartActivity(typeof(SettingsActivity));
break;
}
};
I would like to extract this piece of code into external function and just call it from every activity only with Resource.Id... parameter.
How can I do that?
Instead of the inheritance that David Christopher Reynolds suggests here. I would instead consider using composition instead. This way you don't accidentally end up creating this "God"-object of a base class that you start inheriting from everywhere.
Instead, make a strategy:
public interface INavigationStrategy
{
void Navigate(int selectedItem);
}
public class BottomBarNavigationStrategy : INavigationStrategy
{
private Context _context;
public BottomBarNavigationStrategy(Context context)
{
_context = context;
}
public void Navigate(int selectedItem)
{
switch(selectedItem)
{
case Resource.Id.navigationBarMain:
_context.StartActivity(typeof(MainActivity));
break;
case Resource.Id.navigationBarHistory:
_context.StartActivity(typeof(HistoryActivity));
break;
case Resource.Id.navigationBarSettings:
_context.StartActivity(typeof(SettingsActivity));
break;
}
}
}
Then you can use it in your code like:
var navigationStrategy = new BottomBarNavicationStrategy(this);
bottomNavigationBar.NavigationItemSelected += OnItemSelected;
void OnItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
navigationStrategy.Navigate(this, e.Item.ItemId);
}
This way you can easily share code across classes, without inheritance and limiting yourself to using a Base class for everything.
You could create a "BaseActivity" which inherits from the Android activity class, and all your activities inherit from. Then you could put a method in the base activity to do what you want. If you set your Activities to inherit from this activity you will be able to easily call it.
Your base activity would look something like this:
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme.NoActionBar")]
public class BaseActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected virtual void SetBottomNavigationBar(int id)
{
var bottomNavigationBar = FindViewById<BottomNavigationView>(id);
bottomNavigationBar.SelectedItemId = Resource.Id.navigationBarMain; //This id is different for every activity
bottomNavigationBar.NavigationItemSelected += (sender, e) =>
{
switch (e.Item.ItemId)
{
case Resource.Id.navigationBarMain:
StartActivity(typeof(MainActivity));
break;
case Resource.Id.navigationBarHistory:
StartActivity(typeof(HistoryActivity));
break;
case Resource.Id.navigationBarSettings:
StartActivity(typeof(SettingsActivity));
break;
}
};
}
}
Then in your other activities you would do something like this:
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity :BaseActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
//other code
SetBottomNavigationBar(Resource.Id.bottomNavigationBar);
}
}
I see this often:
protected override void OnAppearing()
{
base.OnAppearing();
Why do people add base.OnAppearing()
Also:
protected override void OnStart()
{
base.OnStart();
Is it needed to override OnStart and is that also a similar lifecycle event?
OnAppearing is a virtual method defined in the Xamarin.Forms.Page class
namespace Xamarin.Forms
{
[RenderWith(typeof(_PageRenderer))]
public class Page : VisualElement, ILayout, IPageController, IElementConfiguration<Page>, IPaddingElement
{
// ...
protected virtual void OnAppearing()
{
}
// ...
}
}
and gets also called from the base class
[EditorBrowsable(EditorBrowsableState.Never)]
public void SendAppearing()
{
if (_hasAppeared)
return;
_hasAppeared = true;
if (IsBusy)
{
if (IsPlatformEnabled)
MessagingCenter.Send(this, BusySetSignalName, true);
else
_pendingActions.Add(() => MessagingCenter.Send(this, BusySetSignalName, true));
}
OnAppearing(); // <--- here
Appearing?.Invoke(this, EventArgs.Empty);
var pageContainer = this as IPageContainer<Page>;
pageContainer?.CurrentPage?.SendAppearing();
FindApplication(this)?.OnPageAppearing(this);
}
It is very common to call the base method within an overridden method.
I have implemented a custom region adapter for a ToolBar as explained in this link http://compositewpf.codeplex.com/discussions/250892. I get this error:'ToolBarRegionAdapter' does not contain a constructor that takes 0 arguments.
Here my code:
public class ToolBarRegionAdapter : RegionAdapterBase<ToolBar>
{
public ToolBarRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
protected override void Adapt(IRegion region, ToolBar regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Items.Add(element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Items.Contains(element))
{
regionTarget.Items.Remove(element);
}
}
break;
}
};
}
}
I have overrided the ConfigureRegionAdapterMappings() method in my Bootstrapper (my Bootstrapper inherits from MefBootstrapper). Here the code:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings regionAdapterMappings = base.ConfigureRegionAdapterMappings();
regionAdapterMappings.RegisterMapping(typeof(ToolBar), new ToolBarRegionAdapter());
return regionAdapterMappings;
}
When I compile I get this error:'ToolBarRegionAdapter' does not contain a constructor that takes 0 arguments. Which is actually true, the contructor takes a IRegionBehaviorFactory but I don't have that object in my code. But in the examples I've seen, the region adapter is instantiated without any argument.
Any idea why? Thanks!
While constructor injection is always preferred, when it's not possible, as in your case, go for the service locator...
ServiceLocator.Current.GetInstance<IRegionBehaviorFactory >()
... as is shown in the link you provided, btw...
You are wrong with how you add adapter:
Must be
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings regionAdapterMappings = base.ConfigureRegionAdapterMappings();
regionAdapterMappings.RegisterMapping(typeof(ToolBar), Container.Resolve<ToolBarRegionAdapter>());
return regionAdapterMappings;
}
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've got this activity and have a problem with OnSharedPreferenceChanged not being called.
My use case is that i want to show preference value in preference description. Code below translated is translated from java where works perfectly fine.
[Activity]
public class PrefActivity : PreferenceActivity, ISharedPreferencesOnSharedPreferenceChangeListener
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
AddPreferencesFromResource(Resource.Xml.preferences);
}
protected override void OnResume()
{
base.OnResume();
PreferenceScreen.SharedPreferences.
RegisterOnSharedPreferenceChangeListener(this);
}
protected override void OnPause()
{
base.OnPause();
PreferenceScreen.SharedPreferences.
UnregisterOnSharedPreferenceChangeListener(this);
}
#region ISharedPreferencesOnSharedPreferenceChangeListener implementation
public void OnSharedPreferenceChanged(ISharedPreferences sharedPreferences, string key)
{
Preference pref = FindPreference(key);
if (pref is ListPreference)
{
ListPreference listPref = (ListPreference)pref;
listPref.Summary = listPref.Entry;
}
}
#endregion
}
Iam using Xamarin.Android v4.6.8 code above is my last attempt to make this working ive also tried using PreferenceScreen.PreferenceChange event for handling preference changes but with no results.
Tahnks for help.
Ive found solution! changing
PreferenceScreen.SharedPreferences.
RegisterOnSharedPreferenceChangeListener(this);
to
PreferenceManager.GetDefaultSharedPreferences(this).
RegisterOnSharedPreferenceChangeListener(this);
will do the trick.
I hope that it will help somebody.