OnActivityResult not called after selecting image from gallery - c#

I am trying to allow the users of my Xamarin.Android application to pick an image from their gallery and upload it to the server. The image, once picked, should first be shown in the upload form, and then converted into base64 and sent to the remote web application.
My app uses only one activity containing a FrameLayout, which gets filled with Fragments. The fragment that should handle the module (which is dynamic and depends on a schema it receives from the server) is instantiating the selection control like this:
private View BuildImageUploader()
{
LinearLayout lay = new LinearLayout(Context);
lay.Orientation = Orientation.Horizontal;
lay.LayoutParameters = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
(lay.LayoutParameters as LinearLayout.LayoutParams).SetMargins(0, 30, 0, 0);
ImageView img = new ImageView(Context);
var par = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent, 0.8f);
par.Gravity = GravityFlags.Center;
par.TopMargin = 5;
img.LayoutParameters = par;
Button btn = new Button(Context);
btn.LayoutParameters = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent, 0.2f);
btn.Text = "Scegli";
btn.Click += (s, e) =>
{
Intent intent = new Intent();
intent.SetType("image/*");
intent.SetAction(Intent.ActionGetContent);
StartActivityForResult(Intent.CreateChooser(intent, "Scegli un'immagine"), 0);
};
lay.AddView(img);
lay.AddView(btn);
return lay;
}
I put the same override of OnActivityCreated in both my fragment and MainActivity:
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
}
Yes, it does absolutely nothing and there is currently no way to get the image back, but I plan to handle that with events. The overrides are still empty because I'm testing them by putting breakpoints in them, to check which one gets called. And guess what? None. OnActivityResult is never called neither in the activity nor in the fragment. What am I doing wrong?
Extra info - what I tried so far (none worked):
Changing the request code (the second parameter of StartActivityForResult) to something different - I tried 1, -1 and some random 4 digits numbers as well
Moving the call to StartActivityForResult inside a method in class MainActivity
Changing the event handler lambda into an actual method
Finally, I would like to emphasize that the image picking phase works like a charm; I can see the gallery and select an image. The problem is that nothing happens after that: OnActivityResult isn't called, and I verified this with breakpoints. Thanks to whoever is willing to help, I need this for work and I'm quite desperate now, so I guess any solution will do.
EDIT: I was asked to include the code that handles the control drawing. I can't include it all (it's more than 1000 lines long and all the other controls work anyway), so I will show the part concerning this specific control:
private void ShowItem(LinearLayout container, DynamicFieldSchemaItem item)
{
GuiControlType type = GuiControlType.GetGuiControlTypeById(item.SystemGuiControlId);
List<View> toAdd = new List<View>();
switch(type.Enum)
{
// Various cases for other controls...
case GuiControlTypeEnum.UploadImage:
toAdd.Add(BuildImageUploader());
break;
}
foreach (View view in toAdd)
container.AddView(view);
}
Here, container is used because the controls are divided in tabs, thus the application creates a separate LinearLayout for each of those tabs and then adds controls to it via ShowItem, called on every item received from the server.
UPDATE: while I was testing an unrelated functionality on a different device, I accidentally tapped on the uploader button, closed it, and the Activity's OnActivityResult breakpoint was toggled. This means that the problem is with my device. I have a OnePlus One with the stock LineageOS file browser and gallery. Could I solve this problem for my device somehow, in case anybody else is running the app with this setup?

1) Start in Fragment, receive results in Activity: use Activity.StartActivityForResult()
2) Start in Fragment, receive results in Fragment: use StartActivityForResult(), and in the Activity's OnActivityResult() call base.OnActivityResult(requestCode, resultCode, data);
Base on the codes you provided, looks like you want to use 2), receive results in Fragment, so, please add OnActivityResult() in your fragment.

Related

Getting an image from a GMap.NET.GMapControl without it being visible

I would like to get a map from an online source (whether than be Google Maps, OpenStreetMap, or other) and be able to (1) display it on a form, and (2) save it as an image. These two functions are separate.
After a bit of research I concluded that the GMap.NET.GMapControl was probably the best way of doing this and I implemented this method. However, I have hit a snag when trying to save the image.
I am saving the image by generating a jpeg from the control using its ToImage() method. This works, but only when the map is visible on screen. In my application I need to be able to generate the image without rendering it to screen.
If the GMapControl is not visible, the jpeg is just a black rectangle. The test code below demonstrates this. The form contains two GMapControl controls. If both are visible I get two identical jpeg images. If one is hidden, the corresponding jpeg is blank.
Is there a way I can get the map image using a GMapControl without plotting it to screen? Or should I take a different approach and use something else? The more lightweight the better as far as I am concerned.
(My first attempt was using the WebBrowser control. I moved on from this because I was getting all the borders etc. as well as the map. I tried to exclude everything but the div containing the map, but then I lost everything ; I suspect this may have been because the map div was nested and I was hiding its parent...)
public partial class testForm : Form
{
public testForm()
{
InitializeComponent();
}
private void testForm_Shown(Object sender, EventArgs e)
{
gMapControl2.Hide(); // this results in a blank jpg image for gMapControl2
// Plot the same map to both gMapControls...
PlotMap(gMapControl1);
PlotMap(gMapControl2);
// Excuse the clunky wait method here ; it was due to a 'cross-thread' error when using the event raised by the gMapControl
// It serves the purpose here.
Task.Factory.StartNew(() => { Task.Delay(5000).Wait(); }).Wait(); // wait for 5 seconds to give maps plenty of time to render
WriteBitmap(gMapControl1, $#"E:\Test_gMapControl1.jpg");
WriteBitmap(gMapControl2, $#"E:\Test_gMapControl2.jpg");
}
private void PlotMap(GMapControl gMapControl)
{
gMapControl.MapProvider = GoogleMapProvider.Instance;
GMaps.Instance.Mode = AccessMode.ServerOnly;
gMapControl.ShowCenter = false;
gMapControl.MinZoom = 1;
gMapControl.MaxZoom = 25;
gMapControl.Zoom = 10;
gMapControl.Position = new PointLatLng(10, 10); // centered on 10 lat, 10 long
}
private void WriteBitmap(GMapControl gMapControl, string filename)
{
Image b = gMapControl.ToImage();
b.Save(filename, ImageFormat.Jpeg);
}
}

Xamarin.android GUI does not update after AddView

I am creating an application for Xamarin.android in c# and ran into an odd problem.
I use a wrapper class which inflates a view. After that I add the view to a linearlayout with AddView. Sometimes the gui doesn’t update for no apparent reason. The Elements aren’t added. When I call the task button (I don’t know if that’s the right name – the button left of the home button) and then navigate back to my app the elements will appear. I read somewhere that you could use Invalidate() to update the gui but that does not seem to work.
Any suggestions are highly appreciated.
This is my constructor of the wrapper class:
public BoxDisplay(MainActivity activity, Product product, ViewGroup root)
{
View = activity.LayoutInflater.Inflate(Resource.Layout.BoxItem, root, false);
shelf = View.FindViewById<TextView>(Resource.Id.Shelf);
name = View.FindViewById<TextView>(Resource.Id.Name);
barcode = View.FindViewById<TextView>(Resource.Id.Barcode);
Stock = View.FindViewById<EditText>(Resource.Id.Stock);
typ = View.FindViewById<TextView>(Resource.Id.type);
this.activity = activity;
SetProduct(product);
Stock.ClearFocus();
Stock.Click += (o, arg) =>
{
Stock.SelectAll();
};
Stock.EditorAction += EditorAction;
}
and here I add the view long after the activity was created:
private void GenerateView(List<Product> products)
{
var boxlist = FindViewById<LinearLayout>(Resource.Id.BoxList);
foreach (var product in products)
{
var box = new BoxDisplay(this, product, boxlist);
boxDisplays.Add(box);
box.EnableEditText(false);
boxlist.AddView(box.View);
box.View.Click += (sender, e) => { box.UpdateBox(); };
}
}
A couple of ideas more than an answer, and maybe could be bad practice, but I do not run into your issue :
I usually add programmatically created Views in OnViewCreated (Fragment) / OnCreate(Activity)
Make sure your LinearLayout has a parent. It must be added to a parent view (root Layout, another Layout, a scrollview, ...).
Make sure you do not have a blocking, manually launched thread involving UI update (even through RunOnUiThread), which could be unlocked by activity recreation.
Make sure your view isn't "flattened" by another existing one in your layout (for example having a view matching parent size in both dimensions)
Beside the fact that it won't display your layout, is your application running properly (no permanent freeze) ?

onCreateView not called on fragment that was inflated by layoutinflator

I have a layout that I want to show as a popup window (used as a custom options menu) while in a fragment of a viewpager. Therefore, when the "Options" button is clicked, I do the following:
public void onOptionsButtonClicked(int buttonHeight)
{
LayoutInflater optionsLayoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
int popupWidth = ViewGroup.LayoutParams.MatchParent;
int popupHeight = ViewGroup.LayoutParams.WrapContent;
View layout = optionsLayoutInflater.Inflate(Resource.Layout.TrackOptions, null);
int popupYOffset = (85 + buttonHeight) * -1;
var popup = new PopupWindow(context);
popup.Focusable = true;
popup.Width = popupWidth;
popup.Height = popupHeight;
popup.ContentView = layout;
popup.SetBackgroundDrawable(new BitmapDrawable());
popup.OutsideTouchable = true;
popup.ShowAsDropDown(view, 0, popupYOffset);
}
And this works as I want, visually that is. Meaning, I click the button and I do see the layout popup as a popup window with all of my options. HOWEVER, none of the buttons work. I put a breakpoint in the class that should be associated the the layout and noticed that onCreateView never gets called, therefore, none of the buttons and associated click event handlers are ever wired up. So, I know why it is not working. However, I don't know how to fix it. I think it is because, while I inflate the view, I am never actually creating the fragment. I have done fragementmanager transactions to replace a fragment in other parts of my project and I know that would probably do it, however, this is a different case as I am trying to do a popup window.
Thanks!
Mike
Fragment is attach in activity so you can try it in onActivityCreated(Bundle) method

Prevent activity from reload after rotation in xamarin, monodroid

Ok... So my problem is to prevent from activity to reload after orientation is changed.
Basically, what I did is this:
[Activity(Label = "migs", ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation)]
This is worked fine, until I changed "Target API" to 14. If I'm changing it back to 12, then everything is working, but on 14, activity is being restarted (OnCreate method is fires after rotation).
So... You'll ask why do I need "Target API" 14? - Easy! Because in my app, I'm playing video, and for that I need "true full screen". All API's below 14 adding "Settings" (three dots) button. In case of HTC, it's big and ugly button, that I was unable to get rid of.
If you know how to do one of the two (Get rid of the "settings" button in API 12, or prevent activity from reload after orientation changed in API 14), I'll be very thank full for your help.
Ok... At last I solved it! :)
Saving activity state instead of preventing activity from reload, from first sight can seem to be a little tricky, but in fact is really easy and it's the best solution for situations like this.
In my case, I had a ListView, that populates from the internet with items, that stored in custom list adapter. If device orientation was changed, the activity was reloaded, so does the ListView, and I was loosing all the data.
All I needed to do is to override the OnRetainNonConfigurationInstance method.
Here's a quick sample of how to do it.
First of all, we need a class, that can handle all of our stuff.
Here is a wrapper for all the things we need to save:
public class MainListAdapterWrapper : Java.Lang.Object
{
public Android.Widget.IListAdapter Adapter { get; set; }
public int Position { get; set; }
public List<YourObject> Items { get; set; }
}
In our activity, we need to hold variables, to store all the data:
ListView _listView; //Our ListView
List<YourObject> _yourObjectList; //Our items collection
MainListAdapterWrapper _listBackup; //The instance of the saving state wrapper
MainListAdapter _mListAdapter; //Adapter itself
Then, we overriding the OnRetainNonConfigurationInstance method in the activity:
public override Java.Lang.Object OnRetainNonConfigurationInstance()
{
base.OnRetainNonConfigurationInstance();
var adapterWrapper = new MainListAdapterWrapper();
adapterWrapper.Position = this._mListAdapter.CurrentPosition; //I'll explain later from where this came from
adapterWrapper.Adapter = this._listView.Adapter;
adapterWrapper.Items = this._yourObjectList;
return adapterWrapper;
}
And the final stage is to load saved state in OnCreate method:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.list);
this._listView = FindViewById<ListView>(Resource.Id.listView);
if (LastNonConfigurationInstance != null)
{
this._listBackup = LastNonConfigurationInstance as MainListAdapterWrapper;
this._yourObjectList = this._listBackup.Items;
this._mListAdapter = this._listBackup.Adapter as MainListAdapter;
this._listView.Adapter = this._mListAdapter;
//Scrolling to the last position
if(this._listBackup.Position > 0)
this._listView.SetSelection(this._listBackup.Position);
}
else
{
this._listBackup = new MainListAdapterWrapper();
//Here is the regular loading routine
}
}
And about the this._mListAdapter.CurrentPosition... In my MainListAdapter, I added this property:
public int CurrentPosition { get; set; }
And the, in the `GetView' method, I did that:
this.CurrentPosition = position - 2;
P.S.
You don't have to implement exactly as I showed here. In this code, I'm holding a lot of variables, and making all the routine inside the OnCreate method - that is wrong. I did that, just to show how it can be implemented.
what happen when orientation change (consider you enable rotation in your phone) ?
Android restart activity onDestroy() is called, followed by onCreate() , you can distinguish between onDestroy() call to kill activity or restart app throw old answer.
Prevent Activity restart
just set ConfigurationChanges to both Orientation , ScreenSize
[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
why this may be not working ?
I dont think this will not working but set RetaintInstance to true read more about RetainInstance
class myFragment: Fragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
this.RetainInstance = true;
// this to change screen orientation
Activity.RequestedOrientation = ScreenOrientation.Landscape;
}
.....
}
hope this help
Above API 13, you need to include screensize in your ConfigChanges.
As denoted here.
Maybe adding that tag to your activity for API13+ will help?

Persisting User Input for Dynamic Control in SharePoint Web Part

Edit at bottom with solution
I've seen a similar question to this posted before and have tried the suggestions, but I must be missing something. My basic problem is this: I have a select box where the user can select a filter which may or may not have constraints built into it (requires the user to input further data so the filter knows how to filter). Since it's unknown just how many constraints will exist for the filter, I'm trying to load them in dynamically and add them to a placeholder panel that I have. The correct number of constraints load just fine and dandy, but when the user inputs text and hits submit, after the page reloads none of the values persist. Here's the appropriate code (I can provide more if needed):
I have these as class variables for my Web Part:
Panel constraintPanel;
HtmlInputText[] constraints;
Label[] constraintLabels = null;
Inside an override CreateChildControls I initialize the panel:
constraintPanel = new Panel();
I build in the dynamic input boxes in an overridden OnPreRender (Note: I've heard people say to do it in OnInit, OnLoad, or OnPreRender, but OnPreRender is the only one that doesn't make the whole Web Part error out):
protected override void OnPreRender(EventArgs e)
{
buildConstraints();
base.OnPreRender(e);
}
private void buildConstraints()
{
if (!viewSelect.SelectedValue.Equals(INConstants.NoFilterOption))
{
string[,] constraintList = docManager.GetFilterConstraints(viewFilterSelect.SelectedValue);
if (constraintList != null)
{
this.constraints = new HtmlInputText[constraintList.Length / 2];
this.constraintLabels = new Label[constraintList.Length / 2];
for (int constraintCount = 0; constraintCount < constraintList.Length / 2; constraintCount++)
{
Label constraintLabel = new Label();
constraintPanel.Controls.Add(constraintLabel);
constraintLabel.Text = constraintList[constraintCount, 0];
this.constraintLabels[constraintCount] = constraintLabel;
HtmlInputText constraint = new HtmlInputText();
constraintPanel.Controls.Add(constraint);
constraint.ID = "constraint_" + constraintCount;
constraint.MaxLength = 12;
constraint.Style.Add("FONT-FAMILY", "Verdana");
constraint.Style.Add("FONT-SIZE", "11");
this.constraints[constraintCount] = constraint;
}
}
}
}
And then finally inside an overridden RenderWebParts I have (note: I've also tried looping through the arrays constraints and constraintLabels to render the controls, but it made no difference):
...
constraintPanel.RenderBeginTag(output); // not sure if this is needed
if (constraints != null && constraints.Length > 0)
{
foreach (Control tempControl in constraintPanel.Controls)
{
if (tempControl is Label)
{
output.WriteLine("<tr>");
output.WriteLine("<td width='2%' nowrap><font class='search-header'>");
tempControl.RenderControl(output);
output.WriteLine(" ");
}
else if (tempControl is HtmlInputText)
{
tempControl.RenderControl(output);
output.WriteLine("</td>");
output.WriteLine("<td width='*' nowrap></td>");
output.WriteLine("</tr>");
}
}
}
constraintPanel.RenderEndTag(output); // not sure if this is needed
...
I appreciate any help, as this is truly driving me crazy.
Edit with solution:
I've been able to get it working. I needed to override the OnLoad event and wrap my calls from there in a try-catch block. For some reason the initial page load throws an exception when trying to run, which causes the entire page to not display. I also forgot to add my constraintPanel to the Controls list.
Here's the code in OnLoad for information's sake:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
try
{
viewsBuildConstraints();
}
catch (Exception)
{
}
}
Try marking your webpart with the INamingContainer interface and make sure to give all controls an ID. Furthermore, HtmlInput COntrols do not have a viewstate i believe, which would cause them to "forget" the input after a postback. Could you try using actual TextBox controls?

Categories