Fragment getting null in host activity - android

I want to call fragment method from another fragment, so I find relevant fragment in host activity and call method of this fragment. But some time I getting fragment null.
Suppose I want to save data of fragment A from all other fragment. There is 4 fragment like A, B, C and D. When I click save button from fragment A and B then it working fine but When I save data from fragment C and D then Fragment A getting null.
Here is my code :
HostActivity.cs
public class HostActivityView : MvxCachingFragmentCompatActivity<HostActivityViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
try
{
SetContentView(Resource.Layout.HostActivityView);
var toolbar = FindViewById<Toolbar>(Resource.Id.myToolbar);
if (toolbar != null)
{
// Toolbar will now take on default actionbar characteristics
SetSupportActionBar(toolbar);
}
// View Pager
var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
if (viewPager != null)
{
// Add tabs in view pager
var fragments = new List<MvxFragmentStatePagerAdapter2.FragmentInfo>
{
new MvxFragmentStatePagerAdapter2.FragmentInfo("Ticket", typeof(Tab_Ticket), typeof(TicketEditViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Employee", typeof(Tab_Employee), typeof(EmployeeViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Response", typeof(Tab_Correspondence), typeof(ResponseViewModel)),
new MvxFragmentStatePagerAdapter2.FragmentInfo("Expense", typeof(Tab_Expenses), typeof(ExpenseViewModel)),
};
viewPager.Adapter = new MvxFragmentStatePagerAdapter2(this, SupportFragmentManager, fragments);
}
var tabLayout = FindViewById<TabLayout>(Resource.Id.tabs);
tabLayout.SetupWithViewPager(viewPager);
}
catch (Exception ex)
{
Mvx.Resolve<IUserInteraction>().Alert(ex.Message);
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.menu_accept:
Tab_Ticket tabTicket = (Tab_Ticket)SupportFragmentManager.FindFragmentByTag("Tab_Ticket");
if (tabTicket != null)
{
tabTicket.OnOptionsItemSelected(item);
handled = true;
}
break;
}
}
}
Here is my fragment Code :
Fragment.cs :
public class Tab_Ticket : MvxFragment<TicketEditViewModel>
{
public override View OnCreateView(Android.Views.LayoutInflater inflater, Android.Views.ViewGroup container, Android.OS.Bundle savedInstanceState)
{
var ignored = base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.frg_tab_ticket, null);
return view;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Resource.Id.menu_accept:
this.ViewModel.CmdTicketSave.Execute(null);
return true;
break;
}
}
}
Please suggest me where I going wrong?
Thank you

Finally got the solution for this problem.
In host activity increase the view pager page limit. Because I have 5 fragments and default it display 3 fragments in support fragment manager. So sometime getting first fragment null. Added this line in my code and it working fine.
viewPager.OffscreenPageLimit = 6;
Here is more details about this issue.

Related

How to add an activity in bottom navigation view that already contains fragments

I'm using 4 fragments and the MainActivity (which contains the bottom nav) but now because of a particular payment gateway issue with one of the fragments I have changed that fragment into an activity, but as the bottom nav has 3 fragments in it don't know how do add an activity in it, and I also don't want to change all the 3 remaining fragments into activity just to make it work with bottom nav
Files
MainActivity,Home_Fragment,Caution_Fragment,Donate_Fragment // this is the fragment i changed into activtiy// and About_Fragment
is there any way to add an activity in a bottom navigation view that already contains fragments
if you want to view the Donate_Fragment(Activity) code please tell me
I will add it
MainActivity.java
public class MainActivity extends AppCompatActivity {
public BottomNavigationView bottomNavigationView;
Deque<Integer> integerDeque = new ArrayDeque<>(3);
boolean flag = true;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Window window = this.getWindow();
window.setStatusBarColor(this.getResources().getColor(R.color.black));
bottomNavigationView = findViewById(R.id.bottomNavigationView);
bottomNavigationView.setItemIconTintList(null);
integerDeque.push(R.id.home_icon);
loadFragments(new Home_Fragment());
bottomNavigationView.setSelectedItemId(R.id.home_icon);
bottomNavigationView.setOnNavigationItemSelectedListener(
item -> {
int id = item.getItemId();
if (integerDeque.contains(id)) {
if (id == R.id.home_icon) {
integerDeque.size();
if (flag) {
integerDeque.addFirst(R.id.home_icon);
flag = false;
}
}
integerDeque.remove(id);
}
integerDeque.push(id);
loadFragments(getFragment(item.getItemId()));
return true;
}
);
}
#SuppressLint("NonConstantResourceId")
private Fragment getFragment(int itemId) {
switch (itemId) {
case R.id.home_icon:
bottomNavigationView.getMenu().getItem(0).setChecked(true);
return new Home_Fragment();
case R.id.guide_icon:
bottomNavigationView.getMenu().getItem(1).setChecked(true);
return new Cuation_Fragment();
case R.id.donate_icon:
bottomNavigationView.getMenu().getItem(2).setChecked(true);
return new Donate_Fragment();
case R.id.about_icon:
bottomNavigationView.getMenu().getItem(3).setChecked(true);
return new About_Fragment();
}
bottomNavigationView.getMenu().getItem(0).setChecked(true);
return new Home_Fragment();
}
public void loadFragments(Fragment fragment) {
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName())
.commit();
}
}
#Override
public void onBackPressed() {
integerDeque.pop();
if (!integerDeque.isEmpty()) {
bottomNavigationView.setSelectedItemId(integerDeque.peek());
} else {
finish();
}
}
}

Android 8 nullpointer exception with function call in fragment (only on certain devices!!!)

I currently see a very strange nullpointer-exception that I can not explain myself.
I have an Activity which hosts two fragments.
The context-menu is evaluated in the host-activity and then calls a public function in one of the fragments.
While this works well for several phones with android 7 I usually get a nullpointer-exception on my Samsung S7 with Android 8.
The nullpointer-exception occurs when I try to access any UI-elements of the fragment within this function call!
I already checked that the fragment instances are valid, and they are ok. They are fully initialized and added on the onCreate of the host.
Whenever I trigger the function from inside the fragment it is ok, but not if I call the same function from the context menu of the hosting activity!
At first it looks like a timing problem, because sometimes it works, although relativ seldom.
What is the reason of this behaviour and how can I get over this strange error?
Thanks
Andreas
public class EpaperFragmentHost extends AppCompatActivity
{
private CustomViewPager mViewPager;
private Toolbar toolbar;
private EpaperPicture_Fragment EpaperPictureFrag;
private int Picture_Fragment_Position = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setContentView(R.layout.activity_connectfragmenthost);
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle( String.format( Locale.GERMAN,
getString(R.string.Connectingto_STRING) , mDeviceName) );
setSupportActionBar(toolbar);
mSectionsPagerAdapter = new Connection_fragment_adapter(
getSupportFragmentManager() );
EpaperPictureFrag = EpaperPicture_Fragment.newInstance( );
mSectionsPagerAdapter.setFragment( Picture_Fragment_Position,
EpaperPictureFrag );
mViewPager = findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.epapermenu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id){
case R.id.menuitem_savepicture:
// This call will fail on some phones, but works on others!
// And not because EpaperPictureFrag would be null, but the
// myDrawView inside this instance is null!
EpaperPictureFrag.takeScreenshot( true);
break;
default:
break;
}
boolean result = super.onOptionsItemSelected(item);
return result;
}
}
and this is the fragment:
public class EpaperPicture_Fragment extends Fragment
{
public DrawView myDrawView;
public EpaperPicture_Fragment() {
// Required empty public constructor
}
public static EpaperPicture_Fragment newInstance( ) {
EpaperPicture_Fragment fragment = new EpaperPicture_Fragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_epaper_picture, container,
false);
btn_Clear = v.findViewById(R.id.clear_btn);
btn_addText = v.findViewById(R.id.addtext_btn);
btn_Transmit = v.findViewById(R.id.transmit_btn);
myDrawView = v.findViewById(R.id.epaper);
btn_Clear.setOnClickListener( this );
btn_addText.setOnClickListener(this);
btn_Transmit.setOnClickListener( this );
return v;
}
#Override
public void onClick(View v) {
switch( v.getId() ){
case R.id.transmit_btn:
// This call works!
takeScreenshot( true );
break;
default:
break;
}
}
public File takeScreenshot(boolean showToast) {
// THIS IS THE PROBLEMATIC SECTION!
// Why can myDrawView be null, if the fragment exists?
myDrawView.setDrawingCacheEnabled(true);
Bitmap cachedBitmap = myDrawView.getDrawingCache();
Bitmap copyBitmap = cachedBitmap.copy(Bitmap.Config.RGB_565, true);
myDrawView.destroyDrawingCache();
// ...
}
}
Ok, the solution is:
I changed the check for storage access in the base-class to a new mode.
And this check caused each activity which was inherited by the baseclass to re-create after checking the access rights!
I found it by generating all overwrite methods of the base class and made a log-output in each. It was a terrible destroy and create-sequence.
Unfortunately I changed my test-phone around the same time...

First fragment in view pager not showing because of rest api delay

I have a navigation drawer activity, with one fragment having a view pager and tabs. All 4 fragments are fetching data from a server. My problem is that the view pager is loading the first 2 fragments therefore my first fragment doesn't show a content at first because of the delay of the rest api. So the second fragment is being created and shown before the data in the first fragment is parsed and shown. How can I solve this?
This is my fragment container
public class FragmentMoviesContainer extends KFragment {
private MainActivity activity;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies_container, container, false);
activity = (MainActivity) getActivity();
assert activity != null;
activity.setVisibleFragment(this);
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
ViewPager mViewPager = rootView.findViewById(R.id.container);
TabLayout tabLayout = rootView.findViewById(R.id.tabs);
mViewPager.setAdapter(mSectionsPagerAdapter);
tabLayout.setupWithViewPager(mViewPager);
return rootView;
}
#Override
public void onResume() {
super.onResume();
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null)
actionBar.setTitle(R.string.movies);
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return replaceFragmentMovies(Constants.STRINGS.UPCOMING);
case 1:
return replaceFragmentMovies(Constants.STRINGS.NOW_PLAYING);
case 2:
return replaceFragmentMovies(Constants.STRINGS.POPULAR);
case 3:
return replaceFragmentMovies(Constants.STRINGS.TOP_RATED);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.coming_soon);
case 1:
return getString(R.string.now_playing);
case 2:
return getString(R.string.popular);
case 3:
return getString(R.string.top_rated);
default:
return "";
}
}
#Override
public int getCount() {
return 4;
}
private FragmentMovies replaceFragmentMovies(String type) {
FragmentMovies fragmentMovies = new FragmentMovies();
fragmentMovies.setType(type);
return fragmentMovies;
}
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
}
#Override
public void update(ModelService service, boolean reload) {
}
}
Here's my fragment showed in the tabs
public class FragmentMovies extends KFragment implements MoviesAdapter.OnLoadMoreListener {
private MainActivity activity;
private ModelService service;
private RecyclerView moviesRv;
private String type;
public void setType(String type) {
this.type = type;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
activity = (MainActivity) getActivity();
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
return rootView;
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
if ((objects != null && !objects.isEmpty()) && (responseID == Constants.UPCOMING || responseID == Constants.NOW_PLAYING || responseID == Constants.POPULAR
|| responseID == Constants.TOP_RATED)) {
Section section = (Section) objects.get(0);
MovieListAdapter adapter = new MovieListAdapter(getContext(), section.getMovieList());
moviesRv.setAdapter(adapter);
}
}
#Override
public void update(final ModelService service, final boolean reload) {
boolean hasConnection = Connection.isNetworkAvailable(getContext());
if (hasConnection && service != null) {
final int responseId = getResponseID();
service.getMovies(type, "", false, responseId, reload);
} else {
// progressBar.setVisibility(View.GONE);
DialogHelper.noConnectionDialog(getContext());
}
}
private int getResponseID() {
switch (type) {
case Constants.STRINGS.UPCOMING:
return Constants.UPCOMING;
case Constants.STRINGS.NOW_PLAYING:
return Constants.NOW_PLAYING;
case Constants.STRINGS.POPULAR:
return Constants.POPULAR;
case Constants.STRINGS.TOP_RATED:
return Constants.TOP_RATED;
default:
return 0;
}
}
#Override
public void onLoadMore(MoviesAdapter adapter) {
}
#Override
public void onResume() {
super.onResume();
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setTitle("Movies");
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
activity.getAddFab().hide();
}
#Override
public void onDestroy() {
super.onDestroy();
}
}
The method update calls the rest api url and fetches the data. This is a framework I created based on AsyncTask. The list of objects then is returned to the fragment parsed in the method onServiceResponse where I create the adapter and show the data. The problem is that the second fragment is being created before the method onServiceResponse of the first fragment.
You should make api call from the first fragment and after getting the result you should make the rest of the calls. Let me know if you need any help with the code. I think this should be straight forward.
After Looking your code, there are Two things to inflate Fragments on to tabs.
Use single Fragment for all tabs.
Use individual fragment for every tab.
in the First case, if you are calling APIs form fragment that kind of problem occurs(As yours).
in the Second case APIs, the call will be in individual fragment and there will not be such kind of problem.
So the first solution to your problem is to use individual fragment for every tab.
And if really want to use single fragment for every tab then maintain the sequence of API calling for every instance of the fragment for every tab.
As you are doing in fragment like:
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
in this case you are calling service and and then you are reading setting your view.
The scenario is that here API call will be in the background but the code below API call will execute. Due to that if the response of API any fragment comes then that fragment view will be populated. So Solution of that scenario is that put your API call method in fragment and then call APIs and maintain calls.
if any help just comments. thanks.
I think the accepted answer is not very explanatory, so for anyone coming across this in future, this is what I did. I am calling my REST API from the on create method of the activity hosting the fragments and viewpager and using a single fragment class for 6 tabs by creating 6 instances of the fragment class. But the catch here is, dont set up the viewpager in onCreate, rather set it after the API call receives a successful response, after the data has been saved inside some object. So now the data is ready to be displayed within the fragment when it is first presented.
You should add this code on your one of your fragment.
Handler().postDelayed({
//api call
}, 3000)
So that two fragment can not do api call at same time when you use viewpager.

Xamarin.android - Pass data to my fragment

i have a List with my services, and on item selected i'm passing service type to my activity ServiceDetail like this:
ServiceActivity
void item_selected(object sender, AdapterView.ItemClickEventArgs e) {
MenuContentItem selectedItem = (MenuContentItem)item[e.Position];
if(selectedItem.Title == "COLLO") {
var activity_go = new Intent(this, typeof(ServiceDetailActivity));
activity_go.PutExtra("service_type", "Collo");
StartActivity(activity_go);
}
if (selectedItem.Title == "SPALLA") {
var activity_go = new Intent(this, typeof(ServiceDetailActivity));
activity_go.PutExtra("service_type", "Spalla");
StartActivity(activity_go);
}
}
ServiceDetailActivity
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.ServiceDetail);
//enable navigation mode to support tab layout
this.ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
AddTab("Introduzione", Resource.Mipmap.Icon, new IntroduzioneFragment());
//intent data
string text = Intent.GetStringExtra("service_type") ?? "Data not available";
IntroduzioneFragment fragment = new IntroduzioneFragment();
// set data to pass to my fragment
Bundle bundle = new Bundle();
bundle.PutString("text", text);
fragment.Arguments = bundle;
}
// MY FRAGMENT - I would like "CUSTOM" my fragment "IntroduzioneFragment" like this:
class IntroduzioneFragment : Fragment {
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
base.OnCreateView(inflater, container, savedInstanceState);
var view = inflater.Inflate(Resource.Menu.Tab, container, false);
var sampleTextView = view.FindViewById<TextView>(Resource.Id.textView);
var imageView = view.FindViewById<ImageView>(Resource.Id.image_view);
imageView.SetImageResource(Resource.Mipmap.slide1);
// Get type of service
var test = Arguments.GetString("text");
if (test == "Collo") {
sampleTextView.Text = "is collooooo";
} else {
sampleTextView.Text = "is not collo";
}
return view;
}
}
I don't want create one activity for each service, i would like just have one "Service activity detail" and custom text and image by service type.
ERROR: when I select item service:
System.NullReferenceException - Object reference not set to an instance of an object. on:
var test = Arguments.GetString("text");
You have two ways of doing that.
If that is the activity that holds the fragment, you can call
this.Activity
inside fragment and just call any method of the activity after casting
AwesomceActivty castetActivity = (AwesomeActivity)this.Activity;
castetActivity.AwesomeMethod(12);
Or you can do that by using Delegates:
Define delegates in your Fragment class
namespace Awesome.Android {
public class AwesomeFragment : Fragment {
public delegate void OnAwesomePress (int number);
public event OnAwesomePress sendOnAwesomePressEvent;
}
}
You can assign it when you create a Framgent
AwesomeFragment fragment = new AwesomeFragment ();
fragment.OnAwesomePress += OnAwesomePress;
After that, you implement OnAwesomePress in your activity
private void OnAwesomePress (int number) {
}
Now, when you call sendOnAwesomePressEvent in your Fragment, that event will be passed to Activity.
sendOnAwesomePressEvent (10);

How to save selected tab when rotating screen/changing tab on Xamarin.Android?

I'm using Xamarin Studio and developing a small test project for Android.
I have an Activity with three Tabs on it, each Tab have a different Fragment. So far I got the hang of how to add Tabs and event handlers.
But when I rotate the screen, the default Tab I set is selected which causes the Fragment assigned to that Tab to be displayed.
Another problem I face is that when I change Tabs, I want to preserve the state of the previous Tab, so when I select it again it won't be rendered again. For example, one of my Tabs is a GridView which loads remote images in its cells. When I switch Tabs I don't want for the images to be loaded again.
My main Activity looks like this:
public class MainActivity : Activity
{
private ActionBar.Tab UploadImageTab;
private ActionBar.Tab ImgurTwitterTab;
private ActionBar.Tab RecentImagesTab;
private int selected_tab;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
if (bundle != null) {
selected_tab = bundle.GetInt ("selected_tab", 0);
Log.Debug (GetType ().FullName, "selected tab was " + selected_tab);
}
if (ActionBar != null) {
InitializeActionBar ();
}
SetContentView (Resource.Layout.Main);
}
protected override void OnSaveInstanceState (Bundle outState)
{
Log.Debug (GetType ().FullName, "Saving state tab selected " + selected_tab);
outState.PutInt ("selected_tab", selected_tab);
base.OnSaveInstanceState (outState);
}
protected void InitializeActionBar(){
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
AddTab (UploadImageTab, Resources.GetString (Resource.String.upload_image), Resource.Drawable.ic_upload, new UploadImageFragment(), 1);
AddTab (ImgurTwitterTab, Resources.GetString (Resource.String.imgur_twitter), Resource.Drawable.ic_com, new ImgurOnTwitterFragment(), 2);
AddTab (RecentImagesTab, Resources.GetString (Resource.String.recent_images), Resource.Drawable.ic_gallery, new RecentImagesFragment(), 3);
if (selected_tab == 0) {
Log.Debug (GetType ().FullName, "No value found");
ActionBar.SelectTab (UploadImageTab);
} else {
if (selected_tab == 1) {
Log.Debug (GetType ().FullName, "Selecting tab 1");
ActionBar.SelectTab (UploadImageTab);
} else if (selected_tab == 2) {
Log.Debug (GetType ().FullName, "Selecting tab 2");
ActionBar.SelectTab (ImgurTwitterTab);
}else if(selected_tab == 3){
Log.Debug (GetType ().FullName, "Selecting tab 3");
ActionBar.SelectTab (RecentImagesTab);
}
}
}
protected void AddTab(ActionBar.Tab tab, string tabText, int iconResourceId, Fragment fragment, int index){
tab = ActionBar.NewTab ();
tab.SetText (tabText);
tab.SetIcon (iconResourceId);
tab.TabSelected += delegate(object sender, ActionBar.TabEventArgs e) {
e.FragmentTransaction.Replace(Resource.Id.fragmentContainer, fragment);
if(ActionBar.SelectedTab.Position == 0){
selected_tab = 1;
}else if(ActionBar.SelectedTab.Position == 1){
selected_tab = 2;
}else if(ActionBar.SelectedTab.Position == 2){
selected_tab = 3;
}
Log.Debug(GetType().FullName, "selection is " + selected_tab);
};
ActionBar.AddTab (tab);
}
}
For starters I tried to save the selected Tab. But when I rotate the device, for some reason the TabSelected event on the first Tab (UploadImageTab in this case) is fired, causing the saved value I had to be overwritten.
On the example for my Fragment with a GridView, my code is like this:
public class RecentImagesFragment : Fragment
{
private GridView collectionView;
public List<Photo> photos;
public static float DENSITY;
public override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Console.WriteLine ("is this called every time I switch tabs");
base.OnCreateView (inflater, container, savedInstanceState);
var view = inflater.Inflate (Resource.Layout.RecentImagesTab, container, false);
DENSITY = Activity.Resources.DisplayMetrics.Density;
collectionView = view.FindViewById<GridView> (Resource.Id.collectionView);
collectionView.ItemClick += ItemClick;
photos = new List<Photo> ();
MakeRequest ();
return view;
}
public void ItemClick(object sender, AdapterView.ItemClickEventArgs args){
Console.WriteLine ("photo selected " + photos [args.Position].OriginalUrl);
Intent intent = new Intent (this.Activity, typeof(PhotoDetail));
intent.PutExtra ("url", photos [args.Position].OriginalUrl);
StartActivity (intent);
}
public void MakeRequest(){
var request = (HttpWebRequest)WebRequest.Create("https://api.imgur.com/3/gallery/hot/viral/0.json");
request.Headers.Add ("Authorization", "Client-ID " + "XXXXXXXXXXX");
request.Method = "GET";
Task<WebResponse> task = Task.Factory.FromAsync (
request.BeginGetResponse,
asyncResult => request.EndGetResponse (asyncResult),
(object)null);
task.ContinueWith (t => ReadStreamFromResponse (t.Result));
}
private void ReadStreamFromResponse(WebResponse response){
using (Stream responseStream = response.GetResponseStream ()) {
using (StreamReader sr = new StreamReader (responseStream)) {
string content = sr.ReadToEnd ();
Console.WriteLine (content);
try{
var json = JsonObject.Parse (content);
var array = json ["data"];
foreach (JsonObject o in array) {
string url = o ["link"];
bool isAlbum = o ["is_album"];
if (!isAlbum) {
var short_url = url.Insert (url.Length - 4, "s");
photos.Add (new Photo{ OriginalUrl = url, SmallThumbUrl = short_url });
}
}
} catch(Exception ex){
Console.WriteLine ("Error: " + ex.Message);
}
if (photos.Count > 0) {
Activity.RunOnUiThread (() => {
collectionView.Adapter = new ImageAdapter (this.Activity, photos);
});
}
}
}
}
}
When the view is created I make a HTTP request to Imgur for the latest images url, then I assign the List of Photo objects I create to my ImageAdapter that will download/render them. But these objects are lost when I switch Tabs.
How can I make sure I save the state of my Fragments? And how do I save the state of my Fragment's GridView adapter?
I was able to find an basic example here which helped me dealing with the situation I'm facing. I made the following changes to my code (comments will explain the functionality):
MainActivity.cs
public class MainActivity : Activity
{
private ActionBar.Tab UploadImageTab;
private ActionBar.Tab ImgurTwitterTab;
private ActionBar.Tab RecentImagesTab;
private int selected_tab;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Initialize Action Bar
InitializeActionBar ();
// Check if bundle is different from null, then load saved state and set selected tab
if (bundle != null) {
selected_tab = bundle.GetInt ("selected_tab", 0);
ActionBar.SetSelectedNavigationItem (selected_tab);
Log.Debug (GetType ().FullName, "selected tab was " + selected_tab);
}
}
// Save the selected tab
protected override void OnSaveInstanceState (Bundle outState)
{
Log.Debug (GetType ().FullName, "Saving state tab selected " + this.ActionBar.SelectedNavigationIndex);
outState.PutInt ("selected_tab", this.ActionBar.SelectedNavigationIndex);
base.OnSaveInstanceState (outState);
}
// Initialize Action Bar
protected void InitializeActionBar(){
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
// First big change
// Pass to AddTab method a tab instace, tab text, icon and a tag
AddTab<UploadImageFragment> (UploadImageTab, Resources.GetString (Resource.String.upload_image), Resource.Drawable.ic_upload, "upload");
AddTab<ImgurOnTwitterFragment> (ImgurTwitterTab, Resources.GetString (Resource.String.imgur_twitter), Resource.Drawable.ic_com, "tweets");
AddTab<RecentImagesFragment> (RecentImagesTab, Resources.GetString (Resource.String.recent_images), Resource.Drawable.ic_gallery, "recent");
}
// AddTab now handles generic types that inherit from Fragment
protected void AddTab<T> (ActionBar.Tab tab, string tabText, int iconResourceId, string tag) where T : Fragment{
tab = ActionBar.NewTab ();
tab.SetText (tabText);
tab.SetIcon (iconResourceId);
// tag will help us id this tab
tab.SetTag (tag);
// Get instance of Fragment if it exists
T existing = (T)FragmentManager.FindFragmentByTag (tag);
// Set listener for tab
tab.SetTabListener(new ActivityTabListener<T>(this, tag, existing));
ActionBar.AddTab (tab);
}
}
ActivityTabListener.cs
// Tab listener for generic type that inherits from Fragment
public class ActivityTabListener<T> : Java.Lang.Object, ActionBar.ITabListener where T : Fragment{
// Instance of current context
private Activity context;
// Reference to fragment to be displayed
private Fragment fragment;
// Name of Fragment class
private string fragmentName;
// Tag for tab
private string tag;
// Base constructor requires an Activity instance
public ActivityTabListener(Activity context){
this.context = context;
this.fragmentName = typeof(T).Namespace.ToLower() + "." + typeof(T).Name;
}
// Second constructor receives context, tag and existing fragment instance if available
public ActivityTabListener(Activity context, string tag, T existingFragment = null) : this(context){
this.fragment = existingFragment;
this.tag = tag;
}
// if fragment instance is null then create instance from generic type
// else just attach the fragment
public void OnTabSelected(ActionBar.Tab tab, FragmentTransaction ft){
if (fragment == null) {
fragment = (T)global::Android.App.Fragment.Instantiate (this.context, this.fragmentName);
// if there's a tag then add the fragment to its container and tag it
// else just fragment
if (this.tag != null) {
ft.Add (Resource.Id.fragmentContainer, fragment, tag);
} else {
ft.Add (Resource.Id.fragmentContainer, fragment);
}
} else {
ft.Attach (fragment);
}
}
// if fragment is not null then detach it
public void OnTabUnselected(ActionBar.Tab tab, FragmentTransaction ft){
if (fragment != null) {
ft.Detach (fragment);
}
}
public void OnTabReselected(ActionBar.Tab tab, FragmentTransaction ft){
}
// if disposing the dispose of fragment
protected override void Dispose (bool disposing)
{
if (disposing)
this.fragment.Dispose ();
base.Dispose (disposing);
}
}
These are the important parts for making sure the state of each Fragment on the Activity is persistent when making a configuration change (changing tab, changing orientation, etc).
Now you only need for each Fragment subclass you create to retain their instance and whatever parameters you were using (a list filled by a HTTP request, an adapter, etc) to be reassigned to where they belong (DON'T REINITIALIZE YOUR VARIABLES OR YOU WON'T RETAIN THE SAME VALUES).
Each Fragment subclass must have the following on its OnCreate method:
public override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
// Don't call this method again
RetainInstance = true;
// whatever code you need on its first creation
}
Then you need to make sure your OnCreateView handles the logic to display the view with the data you want, for example if you have a fragment with a List View then you'd be wanting to have a reference to its adapter and its content, then when the view its being created check if any of those is null, if it is then you need to follow your logic to initialize them, else reassign them to the view that will be displayed:
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView (inflater, container, savedInstanceState);
var view = inflater.Inflate (Resource.Layout.some_layout, container, false);
some_list_view = view.FindViewById<ListView> (Resource.Id.some_list_view);
// since the state of this object is retained then check if the list that holds the objects for the list view is not null
// else then just reassing the adapter to the list view
if (some_list == null) {
some_list = new List<SomeObject> ();
// make a HTTP request, load images, create adapter, etc
} else {
some_list_view.Adapter = someAdapter;
}
return view;
}
With this you can avoid your fragments from losing their state when you change tabs or change orientation.
In Fragment Tab:
`#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt('tabSelected', viewPager.getCurrentItem());
}`
Inside onCreateView insert:
tabSelected=savedInstanceState.getInt("tabSelected", 0);
` #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_config_assoc_tab, container, false);
viewPager = rootView.findViewById(R.id.viewpager_config);
viewPager.setPagingEnabled(false);
viewPager.setOffscreenPageLimit(0);
**if (savedInstanceState != null) {
tabSelected=savedInstanceState.getInt("tabSelected", 0);
}**
....
....
`
Now after rotating you will be tabSelected to the tab position. tabSelected is a global var.

Categories

Resources