Prevent activity from reload after rotation in xamarin, monodroid - c#

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?

Related

OnActivityResult not called after selecting image from gallery

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.

Cocossharp Template not working

I'm new to cocossharp. I installed cocossharp templates for visual studio, when i select a new cocossharp android game, and run the application, all I get is a black screen with a logo at the top. From the code, I believe i am supposed to get a blue screen with a label written
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our game view from the layout resource,
// and attach the view created event to it
CCGameView gameView = (CCGameView)FindViewById(Resource.Id.GameView);
gameView.ViewCreated += LoadGame;
}
void LoadGame(object sender, EventArgs e)
{
CCGameView gameView = sender as CCGameView;
if (gameView != null)
{
var contentSearchPaths = new List<string>() { "Fonts", "Sounds" };
CCSizeI viewSize = gameView.ViewSize;
int width = 1024;
int height = 768;
// Set world dimensions
gameView.DesignResolution = new CCSizeI(width, height);
// Determine whether to use the high or low def versions of our images
// Make sure the default texel to content size ratio is set correctly
// Of course you're free to have a finer set of image resolutions e.g (ld, hd, super-hd)
if (width < viewSize.Width)
{
contentSearchPaths.Add("Images/Hd");
CCSprite.DefaultTexelToContentSizeRatio = 2.0f;
}
else
{
contentSearchPaths.Add("Images/Ld");
CCSprite.DefaultTexelToContentSizeRatio = 1.0f;
}
gameView.ContentManager.SearchPaths = contentSearchPaths;
CCScene gameScene = new CCScene(gameView);
gameScene.AddLayer(new GameLayer());
gameView.RunWithScene(gameScene);
}
}
public class GameLayer : CCLayerColor
{
// Define a label variable
CCLabel label;
public GameLayer() : base(CCColor4B.Blue)
{
// create and initialize a Label
label = new CCLabel("Hello CocosSharp", "Fonts/MarkerFelt", 22, CCLabelFormat.SpriteFont);
// add the label as a child to this Layer
AddChild(label);
}
protected override void AddedToScene()
{
base.AddedToScene();
// Use the bounds to layout the positioning of our drawable assets
var bounds = VisibleBoundsWorldspace;
// position the label on the center of the screen
label.Position = bounds.Center;
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
// Perform touch handling here
}
}
}
I put a break point in the method thats called when the event ViewCreated is fired, the breakpoint is never hit. I tried creating the CCGameView first then then registering the eventhandler because I thought the event was firing before registering
CCGameView gameView = new CCGameView(this);
gameView.ViewCreated += LoadGame;
gameView = (CCGameView)FindViewById(Resource.Id.GameView);
then I tried calling the LoadGame method directly
CCGameView gameView = (CCGameView)FindViewById(Resource.Id.GameView);
gameView.ViewCreated += LoadGame;
LoadGame(gameView, EventArgs.Empty);
but this resulted in a null exception for the gameView.ContentManager.
My only other suspicion is the emulator itself, perhaps it needs something installed extra, however for a normal xamarin android project it works perfectly. Iv also tried looking at the various examples on Xamarin but they all use Application Delegate, which if i'm not mistaken, was the old way of doing thing. If anyone can help, Id appreciate it. Thanks
It was an emulator issue, had to check the Use Host GPU option on the emulator. On the Android Virtual Device Manager where I can select my created emulators, I selected an emulator I had created, then instead of starting it, I first Edited it, that's where I found the option (Since I had already created some emulators). The answer is here

Best practice for updating a view with data/content?

In Android, when should (and shouldn't) a view (activity/fragment) be updated with content?
TextView's shouldn't be set with text from the same thread OnCreate() is running on, correct, but instead set on the UI thread (which is done by posting statements to the UI threads MessageQueue)?
95% of my view's set data (in TextViews, TabHosts, Checkboxes, etc... ) directly in OnCreate() (or a function called from it) and works fine, but I realize now that just b/c I've been getting away with it doesn't mean it's right (yes i'm getting bit now).
However, running on the UI thread isn't enough, I'm finding I need to post from OnResume() for 100% guarantee that the RecordView will updated as expected.
The code below show's the two scenarios for calling UpdateView(). Is it enough to call UpdateView() in OnResume() to ensure the RecordView will display with populated data as expected, or is there a better, more correct and/or preferred way of doing this?
Also, is _thisView redundant to _container?
Is the RecordView that is having it's OnCreate() being called the same RecordView that is displayed? Why do I need to inflate the RecordView in OnCreate(), and then return it out? Shouldn't this happen automatically by the runtime, before OnCreate() is called (it's almost like a Factory pattern or something)?
Example Code:
public class RecordView : Fragment
{
private Bundle _bundle;
private ViewGroup _container;
private LayoutInflater _inflater;
ViewGroup _thisView;
TextView _tvTitle, _tvField1, _tvField2;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)
{
base.OnCreateView(inflater, container, bundle);
_inflater = inflater;
_container = container;
_bundle = bundle;
Render();
return _thisView;
}
public override void Render()
{
_thisView = (ViewGroup) _inflater.Inflate(Resource.Layout.Record, _container, false);
_tvTitle = _thisView.FindViewById<TextView>(Resource.Id.tv_title);
_tvField1 = _thisView.FindViewById<TextView>(Resource.Id.tv_field_1);
_tvField2 = _thisView.FindViewById<TextView>(Resource.Id.tv_field_2);
// *A* SOMETIMES WORKS - Title & fields sometimes blank
UpdateView();
// *B* SOMETIMES WORKS - Title & fields sometimes blank
_thisView.Post(() => { UpdateView(); });
}
public override void OnResume()
{
base.OnResume();
// *C* SOMETIMES WORKS - Title & fields sometimes blank
UpdateView();
// *D* ALWAYS WORKS - Title & fields always display data as expected
_thisView.Post(() => { UpdateView(); });
}
private void UpdateView()
{
_tvTitle.Text = "Todo";
_tvField1.Text = "RTFM";
_tvField2.Text = "ASAP";
}
}
You should show initial data or some loading animation in OnCreateView(..), load data in background and then post actual content on the main thread once it is available, i.E.:
new Handler(Looper.GetMainLooper()).post(() => {
//update views here
});
Also, setting a View's content in OnCreateView(..) ALWAYS works if you use one of the View.set(..) methods, in your case TextView.setText(..).
That is because setters call invalidate() on the view which in turn redraws the view.
It is by design not possible that setting a TextView's text in OnCreateView(..) is not updated on screen.
I FORGOT TO MENTION
No, _thisView is not the same as _container.
_thisView is a direct child of _container.
Consider this:
Your main layout.xml is a FrameLayout.
you add a Fragment to it that has a layout called frag.xml, beeing a TextView.
now the _container is the FrameLayout, but _thisView is the TextView.

Share data (string) between activities, save and add it to ListView C# Xamarin

first I have to mention that I'm new to Android app development. I'm using Xamarin (C#) for developing apps. Here's the problem. I want to write simple ToDoList application to learn how to store/share data between activities.
Button "Add" is placed in Main layout. When user clicks it, another screen shows up where user can name(EditText) task and add its description(EditText). When user clicks button "Complete" in that screen, app redirects user to Main screen and add user's task to a listview.
I did some research and found out that I could do that with SharedPreferences, saving data to file on device, or sql.
I decided to go with SharedPreferences. Problem is, everything I found about SharedPrefences (and I searched a lot, believe me) is written in Java which wouldn't be such a big problem, but nobody ever mentioned sharing data between different activites using shared preferences. My code is below, I'm really struggling with this over a day, so please help me.
My MainActivity (where button "Add" is):
public class MainActivity : Activity
{
Button add;
ListView list;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
///set layout
SetContentView(Resource.Layout.Main);
///define variables, buttons, lists
add = FindViewById<Button>(Resource.Id.add);
list = FindViewById<ListView>(Resource.Id.list);
///get string from secondActivity
var prefs = Application.Context.GetSharedPreferences("todolist", FileCreationMode.Private);
var preference = prefs.GetString("task", null);
///add string (preference) to list
List<string> lista = new List<string>();
lista.Add(preference);
///"convert" list to listview
ArrayAdapter<string> adapter = new ArrayAdapter<string>(this, Resource.Layout.Main, lista);
list.Adapter = adapter;
add.Click += delegate
{
StartActivity(new Intent(this, typeof(addActivity)));
};
}
}
My second screen (second activity):
public class addActivity : Activity
{
/// define variables and create list
Button complete;
EditText name, description;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.add);
///variables
complete = FindViewById<Button>(Resource.Id.complete);
name = FindViewById<EditText>(Resource.Id.name);
description = FindViewById<EditText>(Resource.Id.description);
///converting to string
string title = name.Text.ToString();
string content = description.Text.ToString();
///pass string to MainActivity
complete.Click += delegate
{
if (String.IsNullOrEmpty(title))
{
Toast.MakeText(this, "Please enter task name", ToastLength.Short).Show();
}
else
{
var prefs = Application.Context.GetSharedPreferences("todolist", FileCreationMode.Private);
var prefEditor = prefs.Edit();
prefEditor.PutString("task", title);
prefEditor.Commit();
Toast.MakeText(this, "Task added", ToastLength.Short).Show();
StartActivity(new Intent(this, typeof(MainActivity)));
}
};
}
}
You could use an SQLite database here. I would recommend doing so if you want to build a ToDo list. It will make it easier for you as well.
Xamarin has plenty of documentation on how to set this up here:
https://developer.xamarin.com/guides/cross-platform/application_fundamentals/data/
They also have an example app that does pretty much what you want to do here:
https://github.com/xamarin/mobile-samples/tree/master/Tasky
You can pass the string to the next activity through an Intent
Instead of defining that intent in the arg of your Start activivty you should define it before that and then add the string using the putExtra method
examples: How to use putExtra() and getExtra() for string data

Custom tree cell renderers in GTK# - How to get cell data?

I'm using a TreeView in GTK# and now I need to use a custom cell renderer.
Although I got almost everything to work, there's one missing piece: The cell renderer must know what to render.
The data to render is a custom class. I want to retrieve a field from that class.
Here's what I have so far
public class TreeItemCellRenderer : CellRenderer
{
public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height)
{
base.GetSize (widget, ref cell_area, out x_offset, out y_offset, out width, out height);
height = 16;
}
protected override void Render (Gdk.Drawable window,
Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags)
{
base.Render (window, widget, background_area, cell_area, expose_area, flags);
using (var layout = new Pango.Layout(widget.PangoContext)) {
layout.Alignment = Pango.Alignment.Left;
layout.SetText("Hello, World!");
StateType state = flags.HasFlag(CellRendererState.Selected) ?
widget.IsFocus ? StateType.Selected : StateType.Active : StateType.Normal;
window.DrawLayout(widget.Style.TextGC(state), cell_area.X, cell_area.Y, layout);
}
}
}
Now I want to draw my custom field instead of "Hello, World!".
Some googling suggested SetProperty/GetProperty is what I need, but:
var col = new TreeViewColumn("TreeItem", renderer);
col.AddAttribute(renderer, "tree-item", 0);
Throws an error on runtime since TreeItemCellRenderer has no property named "tree-item".
Also, it is worth noting that SetProperty is not virtual.
So how should I do it?
Actually, the only problem is that defining the value/data to render in your custom cell renderer is not done in the renderer itself, but outside, in a listener method.
In your widget class, where you define the TreeView and its columns and CellRenderers, you can also define a TreeCellDataFunc, which is nothing but a wickedly hidden name for a listener function that is called before a cell is rendered. But its name already has the "Data" inside and that is what you're supposed to do within that function: Supply the data to render.
TreeItemCellRenderer cellRenderer = new TreeItemCellRenderer();
column.SetCellDataFunc(cellRenderer, new Gtk.TreeCellDataFunc(AwesomeDataFunction));
/**
* Supplies the TreeItemCellRenderer with data for the current cell.
* */
private void AwesomeDataFunction(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
{
(cell as TreeItemCellRenderer).<some self-declared property> = <any fitting value>;
}
You will likely want use the iter and model to get the data that you want. The actual Render() function of the CellRenderer is called after that function.
All in all, I think that whole TreeView/CellRenderer model is a mess. While it does work and you can do most things with it, it is completely unclear what you are supposed to do to achieve your goals. That one TreeView tutorial is okay, but really only shows the tip of the iceberg. Why no Widgets are used for the cell rendering is beyond me. It would have been far easier.
You just declare a property and use it like in this example.
class CellRendererSimple : CellRenderer
{
//property asigned to by treeviewcolumn
//eg. TreeViewColumn tvc = new TreeViewColumn("Column 0", new CellRendererSimple(),"tree_item",0);
[GLib.Property("tree_item")] //this line seems to be nessasary for the property to be recognised by the treeviewcolumn.
public string tree_item
{
get;
set;
}
protected override void Render(Gdk.Drawable window, Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags)
{
//this seems to be the minimum to render text
GC gc = widget.Style.TextGC(StateType.Normal);
Pango.Layout layout = new Pango.Layout(widget.PangoContext);
layout.SetText(tree_item);//add the text to thelayout.
//this is the actuall rendering
window.DrawLayout(gc, expose_area.X, expose_area.Y, layout);
}
}
I haven't found an actual good answer, but I did find a workaround.
My goal has always been to display information while keeping a "pointer" to the class that owns that value.
So here's what I did:
Declare only one column (the "label")
Append rows, each with two columns - the label to display and the class instance. That way the first row is displayed but the second is hidden
Now, getting the second column of each row will give me the value I want, without having to deal with custom renderers and without having random data appearing in the tree.
I know this doesn't answer the question, but it does solve my particular problem. In case someone else ends up in this page, I hope this information is still helpful. But if you do find a better solution, feel free to post it anyway.

Categories