I'm trying to figure out how to properly set the localized string as Tab Title (or dynamically change the Tab Title) with MVVMCross 6.2+ in Xamarin Android.
How should I set the title of the tab in the simple example app?
Thanks in advance for your help.
Here is the simple example app:
MvvmCrossTabs.Core
HomeViewModel.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using MvvmCross.Commands;
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvvmCrossTabs.Core.ViewModels
{
public class HomeViewModel : MvxNavigationViewModel
{
public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }
public HomeViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
}
private async Task ShowInitialViewModels()
{
await Task.WhenAll(new List<Task>
{
NavigationService.Navigate<Tab1ViewModel>(),
NavigationService.Navigate<Tab2ViewModel>(),
NavigationService.Navigate<Tab3ViewModel>()
});
}
}
}
Tab1ViewModel.cs (Tab2ViewModel.cs, Tab3ViewModel.cs)
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvvmCrossTabs.Core.ViewModels
{
public class Tab1ViewModel : MvxNavigationViewModel
{
public Tab1ViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
}
}
}
App.cs
using MvvmCross.IoC;
using MvvmCross.ViewModels;
using MvvmCrossTabs.Core.ViewModels;
namespace MvvmCrossTabs.Core
{
public class App : MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterAppStart<HomeViewModel>();
}
}
}
MvvmCrossTabs.Android
MainApplication.cs
using System;
using Android.App;
using Android.Runtime;
using MvvmCross.Droid.Support.V7.AppCompat;
using MvvmCrossTabs.Core;
namespace MvvmCrossTabs.Android
{
[Application]
public class MainApplication : MvxAppCompatApplication<MvxAppCompatSetup<App>, App>
{
public MainApplication() : base() { }
public MainApplication(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { }
}
}
home.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/maincontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" />
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="enterAlways"
app:tabGravity="fill"
app:tabMaxWidth="0dp" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</android.support.design.widget.CoordinatorLayout>
tab1.axml (tab2.axml, tab3.axml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_frame"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
</resources>
HomeView.cs
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Support.V7.Widget;
using MvvmCross.Droid.Support.V7.AppCompat;
using MvvmCross.Platforms.Android.Presenters.Attributes;
using MvvmCrossTabs.Core.ViewModels;
namespace MvvmCrossTabs.Android
{
[Activity(Label = "#string/app_name", LaunchMode = LaunchMode.SingleTask, Theme = "#style/AppTheme", MainLauncher = true)]
[MvxActivityPresentation]
public class HomeView : MvxAppCompatActivity<HomeViewModel>
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.home);
// Replaces Action Bar with new Toolbar.
var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
ViewModel.ShowInitialViewModelsCommand.Execute();
}
}
}
Tab1View.cs (Tab2View.cs, Tab3View.cs)
using Android.OS;
using Android.Runtime;
using Android.Views;
using MvvmCross.Droid.Support.V4;
using MvvmCross.Platforms.Android.Binding.BindingContext;
using MvvmCross.Platforms.Android.Presenters.Attributes;
using MvvmCrossTabs.Core.ViewModels;
namespace MvvmCrossTabs.Android.Views
{
[MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs, ViewPagerResourceId = Resource.Id.viewpager, Title = "Tab 1", ActivityHostViewModelType = typeof(HomeViewModel))]
[Register(nameof(Tab1View))]
public class Tab1View : MvxFragment<Tab1ViewModel>
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
return this.BindingInflate(Resource.Layout.tab1, null);
}
}
}
When your tabs are created using the ShowInitialViewModelsCommand, the built-in presenter creates a list of MvxViewPagerFragmentInfo objects, passing in the Title value from the attributes. You can see that happening in the ShowViewPagerFragment method in the MvvmCross source code.
The list of MvxViewPagerFragmentInfo objects is then passed into the MvxCachingFragmentStatePagerAdapter that is created for the ViewPager. You can see that happening here
MvxCachingFragmentStatePagerAdapter inherits from the MvxFragmentPagerAdapter class. Inside the MvxFragmentPagerAdapter class is finally where the Title you provided is actually used. You can see it being used in the GetPageTitleFormatted method here
So in order to change the Title at runtime, you could do the following:
Subclass the default presenter and override the ShowViewPagerFragment method (it is marked virtual) and provide the correct localized title string instead of the one defined in the attribute
Here is an example of how to accomplish this:
1.) Create a custom presenter and override ShowViewPagerFragment
public class LocalizedTabPresenter : MvxAppCompatViewPresenter
{
public LocalizedTabPresenter(IEnumerable<Assembly> androidViewAssemblies) : base(androidViewAssemblies)
{
}
protected override Task<bool> ShowViewPagerFragment(Type view, MvxViewPagerFragmentPresentationAttribute attribute, MvxViewModelRequest request)
{
if (attribute.ViewModelType == typeof(Tab1ViewModel)) {
attribute.Title = "My Localized Title for Tab 1"
}
return base.ShowViewPagerFragment(view, attribute, request);
}
}
2.) In your Setup.cs class, let MvvmCross know to use the custom presenter instead
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
return new LocalizedTabPresenter(AndroidViewAssemblies);
}
Note:
This will only work if you need to set the title only once when the
app launches and is setting up the tabs for the first time.
If you are in a situation where the Title can change multiple times while the app is running, you need to subclass MvxCachingFragmentStatePagerAdapter and override the GetPageTitleFormatted method and provide a more custom implementation that fits your use case.
Hope that helps.
Another possible solution would be to use the IMvxOverridePresentationAttribute interface.
This way you can change these values at runtime from the fragment itself.
To do it, simply implement IMvxOverridePresentationAttribute in your tab fragment, and then return something like this:
public MvxBasePresentationAttribute PresentationAttribute(MvxViewModelRequest request)
{
return new MvxTabLayoutPresentationAttribute(title: _("Results"), viewPagerResourceId: Resource.Id.viewPager, tabLayoutResourceId: Resource.Id.tab_layout, fragmentHostViewType: typeof(HostFragment));
}
I like this approach because I don't need to change anything at app level and I have all the code related to the fragment contained in that fragment specifically.
I've been using this and it has been working perfectly.
Be aware that this.ViewModel property will be null during PresentationAttribute
If you want a more detailed explanation on how it works, have a look at this
You have to do something like that:
if (fragments == null || fragments.Count == 0)
{
_firstFragment= (YourFragmentType)Activator.CreateInstance(typeof(YourFragmentType));
_firstFragment.ViewModel = YourVM;
_secondFragment= (YourFragmentType)Activator.CreateInstance(typeof(YourFragmentType));
_secondFragment.ViewModel = YourVM;
_thridFragment= (YourFragmentType)Activator.CreateInstance(typeof(YourFragmentType));
_thridFragment.ViewModel = YourVM;
// Strings from RESX Localization
fragments = new Dictionary<string, Fragment>
{
{ Strings.first_localized_string, _firstFragment},
{ Strings.second_localized_string, _secondFragment},
{ Strings.thrid_localized_string, _thridFragment}
};
}
viewPager = View.FindViewById<ViewPager>(Resource.Id.viewpager);
adapter = new TabsFragmentPagerAdapter(ChildFragmentManager, fragments);
viewPager.Adapter = adapter;
var tabLayout = View.FindViewById<TabLayout>(Resource.Id.tabs);
tabLayout.SetupWithViewPager(viewPager);
Your adapter:
public class TabsFragmentPagerAdapter : FragmentPagerAdapter
{
private readonly Fragment[] fragments;
private readonly string[] titles;
public TabsFragmentPagerAdapter(FragmentManager fm, Dictionary<string, Fragment> fragments) : base(fm)
{
this.fragments = fragments.Values.ToArray();
this.titles = fragments.Keys.ToArray();
}
public override int Count => fragments.Length;
private String GetCharSeuenceFromString(string s)
{
return new String(s);
}
public override Object InstantiateItem(ViewGroup container, int position)
{
return base.InstantiateItem(container, position);
}
public override void SetPrimaryItem(ViewGroup container, int position, Object #object)
{
base.SetPrimaryItem(container, position, #object);
}
public override Fragment GetItem(int position)
{
return fragments[position];
}
public override ICharSequence GetPageTitleFormatted(int position)
{
return GetCurrentPageTitle(position);
}
private ICharSequence GetCurrentPageTitle(int position)
{
return GetCharSeuenceFromString(titles[position]);
}
}
Happy codding!
P.S. Don't use fragments with generics.
Related
I need to Navigate from one app to another in Xamarin.Forms.
Tried this but didn't work
await Xamarin.Essentials.Launcher.OpenAsync("myapp://com.companyname.myapp1");
Suppose the package name of the app you want to open is com.xamarin.secondapp.
Then you need to add IntentFilter and Exported =true to MainActivity.cs of the app you want to open(com.xamarin.secondapp).
Just as follows.
[Activity(Label = "SearchBarDemos", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true,Exported =true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[
IntentFilter
(
new[] { Android.Content.Intent.ActionView },
Categories = new[]
{
Android.Content.Intent.CategoryDefault,
Android.Content.Intent.CategoryBrowsable
},
DataSchemes = new[] { "myapp" }
)
]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
Note: remember to add code DataSchemes = new[] { "myapp" }.
For the current app, you need to add queries tag for the app you want to open in file manifest.xml.
For example:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.openappapp1">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<application android:label="OpenappApp1.Android" android:theme="#style/MainTheme">
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<queries>
<package android:name="com.xamarin.secondapp" />
</queries>
</manifest>
And the code is:
private void Button_Clicked(object sender, EventArgs e)
{
OpenSecondApp();
}
public async void OpenSecondApp()
{
var supportsUri = await Launcher.CanOpenAsync("myapp://");
if (supportsUri)
await Launcher.OpenAsync("myapp://com.xamarin.secondapp");
}
Try this:
public class Test
{
public async Task OpenRideShareAsync()
{
var supportsUri = await Launcher.CanOpenAsync("myapp://");
if (supportsUri)
await Launcher.OpenAsync("myapp://com.companyname.myapp1");
}
}
I'm trying to binding some data with mvvmcross recyclerView but i'm facing on an issue...Recyclerview always empty.This is my code. I'm pretty sur taht all stup file is correct
HomeView(xml file) :
<!-- language: xml -->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:id="#+id/product_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:scrollbars="vertical"
app:MvxItemTemplate="#layout/product_item_row"
app:MvxBind="ItemsSource Products"/>
</ScrollView>
product_item_row:
<!-- language: xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="#dimen/card_margin"
android:clickable="true"
android:elevation="3dp"
android:foreground="?attr/selectableItemBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="#dimen/card_cover_height"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:src="#drawable/ic_store_24"
android:scaleType="fitXY" />
<TextView
android:id="#+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/thumbnail"
android:lines="2"
android:paddingLeft="#dimen/card_name_padding"
android:paddingRight="#dimen/card_name_padding"
android:paddingTop="#dimen/card_name_padding"
android:textColor="#111"
android:textSize="11dp"
android:text="Name"
app:MvxBind="Text Name"/>
<TextView
android:id="#+id/price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/name"
android:layout_marginRight="10dp"
android:gravity="right"
android:paddingBottom="#dimen/card_price_padding_bottom"
android:textColor="#color/colorAccent"
android:textSize="11dp"
android:text="Price"
app:MvxBind="Text Price"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Ma viewModel(HomeViewModel):
<!-- language: c# -->
public class HomeViewModel : MvxViewModel
{
private readonly ICarouselService _carouselService;
private readonly IProductDataService _productDataService;
private MvxObservableCollection<Carousel> _carousels;
private MvxObservableCollection<Product> _products;
public MvxObservableCollection<Carousel> Carousels
{
get { return _carousels; }
set
{
_carousels = value;
RaisePropertyChanged(() => Carousels);
}
}
public MvxObservableCollection<Product> Products
{
get { return _products; }
set
{
_products = value;
RaisePropertyChanged(() => Products);
}
}
public HomeViewModel(ICarouselService carouselService, IProductDataService productDataService)
{
_carouselService = carouselService;
_productDataService = productDataService;
}
public override async Task Initialize()
{
Products = new MvxObservableCollection<Product>(await _productDataService.GetProducts());
Carousels = new MvxObservableCollection<Carousel>(await _carouselService.GetImages());
}
}
My fragment:
<!-- language: c# -->
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.content_frame, false)]
public class HomeView : BaseFragment<HomeViewModel>, IImageListener
{
protected override int FragmentId => Resource.Layout.HomeView;
CarouselView _carouselView;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.HomeView, container, false);
//var view = this.BindingInflate(Resource.Layout.HomeView, container, false);
_carouselView = view.FindViewById<CarouselView>(Resource.Id.carouselView);
var recyclerView = view.FindViewById<MvxRecyclerView>(Resource.Id.product_recycler);
_carouselView.PageCount = ViewModel.Carousels.Count;
_carouselView.SetImageListener(this);
if (recyclerView != null)
{
recyclerView.HasFixedSize = true;
var layoutManager = new GridLayoutManager(Activity, 2, LinearLayoutManager.Vertical, false);
recyclerView.SetLayoutManager(layoutManager);
}
return view;
}
public void SetImageForPosition(int position, ImageView imageView)
{
Picasso.With(Context).Load(ViewModel.Carousels[position].Image)
//.Placeholder(Resource.Mipmap.ic_launcher)
//.Error(Resource.Mipmap.ic_launcher)
//.Resize(500, 300)
.Fit()
.Into(imageView);
}
}
Did i misse something? Any help would be very much appreciated.
In your HomeView fragment the following line is commented out:
var view = this.BindingInflate(Resource.Layout.HomeView, container, false);
And it appears to be using the system provided LayoutInflater.
In order for MvvmCross to recognize the bindings you defined in the axml layout files, you need to use the MvxLayoutInflater via the this.BindingInflate extension method.
Try modifying your code as such:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.HomeView, null);
_carouselView = view.FindViewById<CarouselView>(Resource.Id.carouselView);
var recyclerView = view.FindViewById<MvxRecyclerView>(Resource.Id.product_recycler);
_carouselView.PageCount = ViewModel.Carousels.Count;
_carouselView.SetImageListener(this);
if (recyclerView != null)
{
recyclerView.HasFixedSize = true;
var layoutManager = new GridLayoutManager(Activity, 2, LinearLayoutManager.Vertical, false);
recyclerView.SetLayoutManager(layoutManager);
}
return view;
}
More context:
https://stackoverflow.com/a/53894136/2754727
Not able to call Sanitize method from javascript in Android 4.4 where as same is working in Android 4.1. we can call Sanitize method from android 4.1 like Foo.sanitize('test message') where as same is not working in 4.4. Can someone please try to resolve how to call sanitize method.
if we add [Export] & [JavascriptInterface] again its working but ideally no need to these annotations to call sanitize.
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Webkit;
using Java.Interop;
using Android.Net;
namespace WebViewJavaScriptInterface
{
[Activity (Label = "Mono WebView ScriptInterface", MainLauncher = true)]
public class JavaScriptInterfaceActivity : Activity
{
const string html = #"<html><body><p>This is a paragraph.</p><button type=""button"" onClick=""Foo.sanitize('test message')"">Click Me!</button></body></html>";
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
WebView view = FindViewById<WebView> (Resource.Id.web);
view.Settings.JavaScriptEnabled = true;
view.SetWebChromeClient (new WebChromeClient ());
view.AddJavascriptInterface ((Java.Lang.Object)new Foo (this), "Foo");
view.LoadData (html, "text/html", null);
}
}
class Foo : Java.Lang.Object, UrlQuerySanitizer.IValueSanitizer
{
public Foo (Context context)
{
this.context = context;
}
Context context;
[Export]
[JavascriptInterface]
public void Bar (string message)
{
Console.WriteLine ("Foo.Bar invoked!");
Toast.MakeText (context, "This is a Toast from C#! " + message.ToString(), ToastLength.Short).Show ();
}
public string Sanitize(string value)
{
Console.WriteLine("Santizie.Bar invoked!");
return string.Empty;
}
}
}
Uncaught TypeError: Object [object Object] has no method 'bar'"
The method should be Bar, not bar.
Please change your html to this:
const string html = #"
<html><body><p>This is a paragraph.</p><button type=""button"" onClick=""Foo.Bar('test message')"">Click Me!</button></body></html>
";
I just want to change my resource.drawable.image into URL, but I don't know how I can do that. I try using bitmap method but I don't know how I can make it array using this method. The purpose of this is I want to get the data from the database and store it into my List<> or array so the output will be like this picture. It's working fine in resource.drawable.image but I want to change into into url value.
By the way, I already stored the column ID from the database. I also try to store the image_link column from the database into my while loop and call it into my List<> but I get error. See my code at below --> Fragment1.cs
All of these codes are working.
fragment_layout.axml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:padding="16dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<GridView
android:id="#+id/grid_view_image_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="110dp"
android:gravity="center"
android:numColumns="auto_fit"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center" />
</FrameLayout>
gridview_layout.axml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="15dp"
android:id="#+id/imageViewGrid"/>
<TextView
android:layout_marginTop="5dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textViewGrid"/>
</LinearLayout>
CustomGridViewAdapter.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.Lang;
namespace testing_code
{
public class CustomGridViewAdapter : BaseAdapter
{
private Context context;
private string[] gridViewString;
private int[] gridViewImage;
public CustomGridViewAdapter(Context context, string[] gridViewstr, int[] gridViewImage)
{
this.context = context;
gridViewString = gridViewstr;
this.gridViewImage = gridViewImage;
}
public override int Count
{
get
{
return gridViewString.Length;
}
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 0;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view;
LayoutInflater inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
if(convertView == null)
{
view = new View(context);
view = inflater.Inflate(Resource.Layout.gridview_layout, null);
TextView txtview = view.FindViewById<TextView>(Resource.Id.textViewGrid);
ImageView imgview = view.FindViewById<ImageView>(Resource.Id.imageViewGrid);
txtview.Text = gridViewString[position];
imgview.SetImageResource(gridViewImage[position]);
}
else
{
view = (View)convertView;
}
return view;
}
//private Bitmap GetImageBitmapFromUrl(string url)
//{
// Bitmap imageBitmap = null;
// using (var webClient = new WebClient())
// {
// var imageBytes = webClient.DownloadData(url);
// if (imageBytes != null && imageBytes.Length > 0)
// {
// imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
// }
// }
// return imageBitmap;
//}
}
}
Fragment1.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using SupportFragment = Android.Support.V4.App.Fragment;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Support.V4.Widget;
using Android.Support.V4.App;
using MySql.Data.MySqlClient;
using System.Data;
namespace testing_code
{
public class Fragment1 : SupportFragment
{
MySqlConnection conn = new MySqlConnection();
string query = "server=localhost;port=3306;database=dbsample;user id=root;password=123";
GridView gridview;
//string[] gridviewstring = {
// "location", "sound", "note"
//};
//int[] imgview =
//{
// Resource.Drawable.ic_dashboard, Resource.Drawable.ic_headset, Resource.Drawable.ic_dashboard
//};
List<string> gridviewstring = new List<string>();
List<int> imgview = new List<int>();
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
View view = inflater.Inflate(Resource.Layout.fragment_layout, container, false);
conn.ConnectionString = query;
//
MySqlCommand cmd = new MySqlCommand("Select * from wp_kdskli23jkposts where ID in (1,2,4)", conn);
try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();
//Read, get and loop the data value from the database
while (reader.Read())
{
string asd = reader["ID"].ToString();
gridviewstring.Add(asd);
}
}
catch(MySqlException ex)
{
Android.Support.V7.App.AlertDialog.Builder except = new Android.Support.V7.App.AlertDialog.Builder(Activity);
except.SetTitle("Please report this to our website(error server timeout)");
except.SetMessage(ex.ToString());
except.SetPositiveButton("Ok", (senderAlert, args) =>
{
except.Dispose();
});
except.Show();
}
finally
{
conn.Close();
}
//gridviewstring.Add("location");
//gridviewstring.Add("music");
//gridviewstring.Add("book");
imgview.Add(Resource.Drawable.ic_dashboard);
imgview.Add(Resource.Drawable.ic_headset);
imgview.Add(Resource.Drawable.ic_dashboard);
string[] GridViewStringArray = gridviewstring.ToArray();
int[] GridImgViewArray = imgview.ToArray();
CustomGridViewAdapter adapter = new CustomGridViewAdapter(Activity, GridViewStringArray, GridImgViewArray);
gridview = view.FindViewById<GridView>(Resource.Id.grid_view_image_text);
gridview.Adapter = adapter;
return view;
}
}
}
I edited your adapter code to use a list of strings
namespace testing_code
{
public class CustomGridViewAdapter : BaseAdapter
{
private Context context;
private List<string> gridViewString;
private List<string> gridViewImage;
public CustomGridViewAdapter(Context context, List<string> gridViewstr, List<string> gridViewImage)
{
this.context = context;
gridViewString = gridViewstr;
this.gridViewImage = gridViewImage;
}
public override int Count
{
get
{
return gridViewString.Count;
}
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return 0;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view;
LayoutInflater inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
if (convertView == null)
{
view = new View(context);
view = inflater.Inflate(Resource.Layout.gridview_layout, null);
TextView txtview = view.FindViewById<TextView>(Resource.Id.textViewGrid);
ImageView imgview = view.FindViewById<ImageView>(Resource.Id.imageViewGrid);
txtview.Text = gridViewString[position];
imgview.SetImageBitmap(GetImageBitmapFromUrl(gridViewImage[position]));
}
else
{
view = (View)convertView;
}
return view;
}
private Android.Graphics.Bitmap GetImageBitmapFromUrl(string url)
{
Android.Graphics.Bitmap imageBitmap = null;
using (var webClient = new System.Net.WebClient())
{
var imageBytes = webClient.DownloadData(url);
if (imageBytes != null && imageBytes.Length > 0)
{
imageBitmap = Android.Graphics.BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
}
}
return imageBitmap;
}
}
}
Then your fragment to use list of URL strings
public class Fragment1 : SupportFragment
{
MySqlConnection conn = new MySqlConnection();
string query = "server=localhost;port=3306;database=dbsample;user id=root;password=123";
GridView gridview;
List<string> gridviewstring = new List<string>();
List<string> imgview = new List<string>();
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.Inflate(Resource.Layout.fragment_layout, container, false);
conn.ConnectionString = query;
//
MySqlCommand cmd = new MySqlCommand("Select * from wp_kdskli23jkposts where ID in (1,2,4)", conn);
try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();
//Read, get and loop the data value from the database
while (reader.Read())
{
string asd = reader["ID"].ToString();
gridviewstring.Add(asd);
}
}
catch (MySqlException ex)
{
Android.Support.V7.App.AlertDialog.Builder except = new Android.Support.V7.App.AlertDialog.Builder(Activity);
except.SetTitle("Please report this to our website(error server timeout)");
except.SetMessage(ex.ToString());
except.SetPositiveButton("Ok", (senderAlert, args) =>
{
except.Dispose();
});
except.Show();
}
finally
{
conn.Close();
}
imgview.Add(" https://www.bellanaija.com/wp-content/uploads/2013/11/Remy-Martins-Pacesetters-VIP-Party-BellaNaija-November2013023-600x398.jpg");
imgview.Add("http://www.allevents.ng/media/2018/at-the-club-with-remy-martins.png?width=375&mode=crop");
imgview.Add("https://i.ytimg.com/vi/iLZz9Becq98/maxresdefault.jpg");
CustomGridViewAdapter adapter = new CustomGridViewAdapter(Activity, gridviewstring, imgview);
gridview = view.FindViewById<GridView>(Resource.Id.grid_view_image_text);
gridview.Adapter = adapter;
return view;
}
}
}
Hope this helps
So I was trying to build a End To End integration testing suite with RavenDB and ServiceStack but I ran into a really weird issue where the validation doesn't run on some requests. This is really strange and I am not sure what I am doing wrong. I am using NCrunch. Sometimes the test passes, sometimes it fails.
Hope this is an easy fix and something bone headed I'm doing.
You can download the whole project on http://github.com/khalidabuhakmeh/endtoend
You don't need anything other than VS2012 and NuGet Package Restore enabled.
UPDATE: I decided to run this in both NCrunch and Resharper Test Runner and both give the same result [see image below].
UPDATE UPDATE: I thought it could be XUnit, so I tried to use NUnit. Nope still the same problem.
**Another Update: Put in console writes as per user1901853's request. This was the result."
Latest Update: the RequestFilters are getting wiped out and I'm not sure why. It seems like it could be a threading issue, but I can't see where.
My AppHost is using AppHostListenerBase.
using EndToEnd.Core;
using Funq;
using Raven.Client;
using ServiceStack.ServiceInterface.Validation;
using ServiceStack.WebHost.Endpoints;
namespace EndToEnd
{
public class TestAppHost
: AppHostHttpListenerBase
{
private readonly IDocumentStore _documentStore;
public TestAppHost(IDocumentStore documentStore)
: base("Test AppHost Api", typeof(TestAppHost).Assembly)
{
_documentStore = documentStore;
}
public override void Configure(Container container)
{
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Register RavenDB things
container.Register(_documentStore);
container.Register(c =>
{
var db = c.Resolve<IDocumentStore>();
return db.OpenSession();
}).ReusedWithin(ReuseScope.Request);
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(CreateWidgetValidator).Assembly);
// todo: register all of your plugins here
AuthConfig.Start(this, container);
}
}
}
My base test class for all of my tests looks like this:
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using ServiceStack.Authentication.RavenDb;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface.Auth;
namespace EndToEnd
{
public abstract class ServiceStackTestBase
: RavenTestBase
{
protected IDocumentStore DocumentStore { get; set; }
protected TestAppHost Host { get; set; }
protected JsonServiceClient Client { get; set; }
protected const string ListeningOn = "http://localhost:1337/";
protected string Username { get { return "testuser"; } }
protected string Password { get { return "password"; } }
protected ServiceStackTestBase()
{
DocumentStore = NewDocumentStore();
IndexCreation.CreateIndexes(typeof(ServiceStackTestBase).Assembly, DocumentStore);
IndexCreation.CreateIndexes(typeof(RavenUserAuthRepository).Assembly, DocumentStore);
Host = new TestAppHost(DocumentStore);
Host.Init();
Host.Start(ListeningOn);
Client = new JsonServiceClient(ListeningOn)
{
AlwaysSendBasicAuthHeader = true,
UserName = Username,
Password = Password
};
RegisterUser();
WaitForIndexing(DocumentStore);
}
private void RegisterUser()
{
Client.Send(new Registration
{
UserName = Username,
Password = Password,
DisplayName = "Test User",
Email = "test#test.com",
FirstName = "test",
LastName = "user"
});
}
public override void Dispose()
{
DocumentStore.Dispose();
Host.Dispose();
}
}
}
my test class looks like this:
using System;
using EndToEnd.Core;
using FluentAssertions;
using ServiceStack.FluentValidation;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface.Auth;
using Xunit;
namespace EndToEnd
{
public class RegistrationTests
: ServiceStackTestBase
{
[Fact]
public void Throws_validation_exception_when_bad_widget()
{
var validator = Host.Container.Resolve<IValidator<CreateWidget>>();
validator.Should().NotBeNull();
try
{
var response = Client.Post(new CreateWidget
{
Name = null
});
// It get's here every once in a while
throw new Exception("Should Not Get Here!");
}
catch (WebServiceException wex)
{
wex.StatusCode.Should().Be(400);
wex.ErrorMessage.Should().Be("'Name' should not be empty.");
}
}
}
}
My code looks like this for the service:
using System;
using Raven.Client;
using ServiceStack.FluentValidation;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.ServiceModel;
namespace EndToEnd.Core
{
[Authenticate]
public class WidgetsService
: Service
{
private readonly IDocumentSession _session;
public WidgetsService(IDocumentSession session)
{
_session = session;
}
public CreateWidgetResponse Post(CreateWidget input)
{
var widget = new Widget { Name = input.Name };
_session.Store(widget);
_session.SaveChanges();
return new CreateWidgetResponse { Widget = widget };
}
}
[Route("/widgets", "POST")]
public class CreateWidget : IReturn<CreateWidgetResponse>
{
public string Name { get; set; }
}
public class CreateWidgetResponse
{
public CreateWidgetResponse()
{
ResponseStatus = new ResponseStatus();
}
public Widget Widget { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
public class Widget
{
public Widget()
{
Created = DateTimeOffset.UtcNow;
}
public string Id { get; set; }
public string Name { get; set; }
public DateTimeOffset Created { get; set; }
}
public class CreateWidgetValidator : AbstractValidator<CreateWidget>
{
public CreateWidgetValidator()
{
RuleFor(m => m.Name).NotEmpty();
}
}
}
I don't have the ability to duplicate your environment but while running in VS2010, using .NET 4, NUnit and ReSharper Test Runner I have not been able to reproduce your issue of 'Validation not firing'. I have run your tests 30+ times. A couple of reasons I can think of Validation not firing would be not having the plugin added or the plugin not registering the validation filters. The 2 if statements below might give you some 'introspection' if either of the cases I listed are the issue. Hope this is somewhat helpful.
if (!TestAppHost.Instance.Plugins.Any(x => x.GetType() == typeof(ValidationFeature)))
{
Console.Write("Validation Plugin is not added");
//TestAppHost.Instance.Plugins.Add(new ValidationFeature());
}
if (!TestAppHost.Instance.RequestFilters.Any(x => x.Target.ToString() == "ServiceStack.ServiceInterface.Validation.ValidationFilters"))
{
Console.Write("No validation request filter");
//TestAppHost.Instance.Container.RegisterValidators(typeof(CreateWidgetValidator).Assembly);
}
Below is my packages.config so you can see the differences in our environments.
<packages>
<package id="FluentAssertions" version="2.0.1" targetFramework="net40" />
<package id="NUnit" version="2.6.2" targetFramework="net40" />
<package id="RavenDB.Client" version="2.0.2261" targetFramework="net40" />
<package id="RavenDB.Database" version="2.0.2261" targetFramework="net40" />
<package id="RavenDB.Embedded" version="2.0.2261" targetFramework="net40" />
<package id="RavenDB.Tests.Helpers" version="2.0.2261" targetFramework="net40" />
<package id="ServiceStack" version="3.9.38" targetFramework="net40-Client" />
<package id="ServiceStack.Authentication.RavenDB" version="3.9.35" targetFramework="net40" />
<package id="ServiceStack.Common" version="3.9.38" targetFramework="net40-Client" />
<package id="ServiceStack.OrmLite.SqlServer" version="3.9.39" targetFramework="net40-Client" />
<package id="ServiceStack.Redis" version="3.9.38" targetFramework="net40-Client" />
<package id="ServiceStack.Text" version="3.9.38" targetFramework="net40-Client" />
<package id="xunit" version="1.9.1" targetFramework="net40" />
</packages>