Switching between ActionBar.Tabs issue - android

I have implemented application which downloads data from web and then shows it in two ActionBar.Tabs. I have one issue with it. If I switch from one tab to other one, application starts another download and while download is not finished, the app freezes. If internet connection is slow it becomes very annoying. I've decide to add ProgressDialog to app to show user that application is downloading data from web. I've added a piece of code which implements ProgressDialog to AsyncTask which performs the download, but that didn't help. I understand why that happens but can't find the way how to fix it :(
Data which will be fitted into tab is represented as instance of Fragment class. This instance after creation will be added to transaction, and only after adding mFragment object to transaction, app switches to another tab.
This is the part of tabListener code:
// ...
#Override
public void onTabSelected(Tab tab, FragmentTransaction transaction) {
if (mFragment == null) {
/*
* Creation of ParkFragment is the reason why app locks!! Because in
* ParkFragment data is being downloaded from web
*/
mFragment = new ParkFragment(mUrl, mActivity);
/*
* mFragment can't be added to transaction until downloading is
* finished, that's why app doesn't switch fast
*/
transaction.add(android.R.id.content, mFragment, mTag);
} else {
transaction.attach(mFragment);
}
}
Please, if anybody has any ideas how to implement ProgressDialog to avoid the delay in switching between tabs, share with it.
Thank you for reading.
Updated: I've read the answer to this question but didn't understand how that implementation will help me managing delays: Changing Tabs is Slow/Laggy - Using Fragments.
UPD:
public class ParkFragment extends ListFragment {
private ArrayList<Cinemas> cinema;
private CinemasAdapter cinemaAdapter;
private String url;
private Activity activity;
public ParkFragment (String cinema,Activity activ){
url = cinema;
activity = activ;
}
public void onCreate(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
cinema = new Handler().handle(url,activity);
cinemaAdapter = new CinemasAdapter(activity, R.layout.movie_data_row, cinema);
setListAdapter(cinemaAdapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
Cinemas movie = cinemaAdapter.getItem(position);
Intent intent = new Intent (activity, More.class);
intent.putExtra("Cinemas", movie);
intent.putExtra("data", movie.getBitmap());
Bundle translateBundle =
ActivityOptions.makeCustomAnimation(activity,
R.anim.slide_in_left, R.anim.slide_out_left).toBundle();
startActivity (intent, translateBundle);
}
}

In your ParkFragment you can use content providers combining with loaders. Downloading data should be handled in a service, when done the service inserts data into DB via content providers. And in your fragment, the loader will load the data.
There is one app named API Demos in any Android emulators, which has several examples related to content providers/ loaders/ services… The source code of its is available in Android SDK, at: [Android SDK]/samples/android-x/ApiDemos, in which x is API level.
I just think so, but if you could share your code of ParkFragment, perhaps there would be another problem?
Edited
In your ParkFragment, you can create a ResultReceiver (available in API 3+), put it into an Intent and start a service to download/ handle the url. The service keeps the instance of the ResultReceiver, when done it sends back the downloaded data to your fragment via send(int, Bundle). A Bundle can hold primitive data types such as String, int, byte[]… For more complex data, you create a class to hold it which implements Parcelable or Serializable.
Or with Bound Services, you can call the service's methods directly from within the fragment. Note that services run on main UI thread, so to avoid of NetworkOnMainThreadException, you need something like Thread in your service.

Related

Start an Activity form a Fragment

I searched in site and there were similar questions as mine but none of theme were not my answer
look at this picture:
so it is clear that i want to start CrimeActivity by sending an intent from CrimeListFragment + an extra in its intent
the book that i read for android programming its author said:
Starting an activity from a fragment works nearly the same as starting an activity from another activity.
You call the Fragment.startActivity(Intent) method, which calls the corresponding Activity
method behind the scenes
CrimeListFragment.java :
public void onListItemClick(ListView l, View v, int position, long id) {
// Get the Crime from the adapter
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
// Start CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
startActivity(i);
}
the second part is now retrieving the intent and its extra and the author said about that:
There are two ways a fragment can access data in its activity’s intent: an easy, direct shortcut and a
complex, flexible implementation. First, you are going to try out the shortcut. Then you will implement
the complex and flexible solution that involves fragment arguments.
and my problem is about the first way, the shortcut
In the shortcut, CrimeFragment will simply use the getActivity() method to access the
CrimeActivity’s intent directly. Return to CrimeFragment and add the key for the extra. Then, in
onCreate(Bundle), retrieve the extra from CrimeActivity’s intent and use it to fetch the Crime
CrimeFragment.java :
public class CrimeFragment extends Fragment {
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
private Crime mCrime;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCrime = new Crime();
UUID crimeId = (UUID)getActivity().getIntent()
.getSerializableExtra(EXTRA_CRIME_ID);
mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
}
The downside to direct retrieval
Having the fragment access the intent that belongs to the hosting activity makes for simple code.
However, it costs you the encapsulation of your fragment. CrimeFragment is no longer a reusable
building block because it expects that it will always be hosted by an activity whose Intent defines an
extra named EXTRA_CRIME_ID.
This may be a reasonable expectation on CrimeFragment’s part, but it
means that CrimeFragment, as currently written, cannot be used with
just any activity.
My question and problem is the last sentence, why this Fragment (CrimeFragment) cannot be used with just any Activity???
The author explains it. Your CrimeFragment, in its onCreate() method, gets its hosting activity (through getActivity()) and then attempts to get an UUID from the Intent used to start that Activity.
This means that any activity containing your CrimeFragment now has to obey this rule, i.e. its intent should have (in it) an extra defined by the name EXTRA_CRIME_ID. If that activity does not comply, you'll see an exception being thrown in CrimeFragment's onCreate().
Try having this fragment in a new activity created by yourself to see what happens.
retrieval in onActivityCreated()
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
....
}
else {
UUID crimeId = (UUID)getActivity().getIntent().getSerializableExtra(EXTRA_CRIME_ID);
}
}

Showing fragment after activity fetches data

I'm fetching data in my activity that is needed by several fragments. After the data is returned, I create the fragments. I was doing this via an AsyncTask, but it led to occasional crashes if the data returned after a screen rotation or the app is backgrounded.
I read up and thought the solution to this was instead using an AsyncTaskLoader. Supposedly it won't callback if your activity's gone, so those errors should be solved. But this now crashes every time because "Can not perform this action (add fragment) inside of onLoadFinished".
How am I supposed to handle this? I don't want my fragments to each have to fetch the data, so it seems like the activity is the right place to put the code.
Thanks!
Edit 1
Here's the relevant code. I don't think the problem is with the code per-se, but more of my whole approach. The exception is pretty clear I shouldn't be creating fragments when I am. I'm just not sure how to do this otherwise.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportLoaderManager().initLoader(BREWERY_LOADER, null, this).forceLoad();
}
//================================================================================
// Loader handlers
//================================================================================
#Override
public Loader<Brewery> onCreateLoader(int id, Bundle args) {
int breweryId = getIntent().getIntExtra(EXTRA_BREWERY_ID, -1);
return new BreweryLoader(this, breweryId);
}
#Override
public void onLoadFinished(Loader<Brewery> loader, Brewery data) {
if (data != null) {
onBreweryReceived(data);
} else {
onBreweryError();
}
}
#Override
public void onLoaderReset(Loader<Brewery> loader) {
}
...
protected void onBreweryReceived(Brewery brewery) {
...
createFragments();
}
...
protected void createFragments() {
FragmentManager fm = getSupportFragmentManager();
//beers fragment
mBeersFragment = (BreweryBeersFragment)fm.findFragmentById(R.id.beersFragmentContainer);
if (mBeersFragment == null) {
mBeersFragment = new BreweryBeersFragment();
fm.beginTransaction()
.add(R.id.beersFragmentContainer, mBeersFragment)
.commit();
Bundle beersBundle = new Bundle();
beersBundle.putInt(BreweryBeersFragment.EXTRA_BREWERY_ID, mBrewery.getId());
mBeersFragment.setArguments(beersBundle);
}
}
Edit 2
My new strategy is to use an IntentService with a ResultReceiver. I null out callbacks in onPause so there's no danger of my activity being hit when it shouldn't be. This feels a lot more heavy-handed than necessary, but AsyncTask and AsyncTaskLoader neither seemed to have everything I needed. Creating fragments in those callback methods doesn't seem to bother Android either.
From the MVC (Model -- View -- Controller) viewpoint, both the Activity and its fragments are Controller, while it is Model that should be responsible for loading data. As to the View, it is defined by the layout xml, you can define custom View classes, but usually you don't.
So create a Model class. Model is responsible for what must survive a screen turn. (Likely, it will be a static singleton; note that Android can kill and re-create the process, so the singleton may get set to null.) Note that Activities use Bundles to send data to themselves in the future.

update listview from fragment within viewpager from main activity

COMPLETELY EDITED
Ok I will try to be more specific.
I'm developing a small app drawer. Therefore I need a way to let the user choose categories.
This is the main point of the question. If there is another better way than my approach please let me know.
So my try to implement this was to load all apps to an array list and save this to a service to make it available throuout the app. The loading of the apps is done by an asynctask.
MainActivity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
packageManager = getPackageManager();
if ((AppDrawerService.getApps()) == null) {
apps = new ArrayList<ApplistItem>();
loadAppsToService();
} else {
//if Service already holds the data
manageViews();
}
}
private void loadAppsToService() {
LoadApplications loadApps;
loadApps = new LoadApplications(this);
loadApps.setOnLoadApplicationsFinishedListener(this);
loadApps.execute();
}
private void manageViews() {
FragmentManager fragmentManager = getSupportFragmentManager();
myViewPager = (ViewPager) findViewById(R.id.view_pager);
myViewPager.setAdapter(new ViewPagerAdapter(fragmentManager, this));
}
#Override
public void OnLoadApplicationsComplete(ArrayList<ApplistItem> apps) {
manageViews();
}
LoadApplications:
#Override
protected void onPostExecute(Object o) {
AppDrawerService.setApps(apps);
listener.OnLoadApplicationsComplete(apps);
super.onPostExecute(o);
}
ViewPagerAdapter:
#Override
public Fragment getItem(int position) {
Fragment category = null;
category = new AppListFragment()
return category;
}
#Override
public int getCount() {
pages = 5;
return pages;
}
AppListFragment:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if ((AppDrawerService.getApps()) == null) {
apps = new ArrayList<ApplistItem>();
} else {
apps = AppDrawerService.getApps();
}
v = getActivity().findViewById(android.R.id.list);
myApplist = (ListView) v;
applistAdapter = new ApplistAdapter(context, 0, apps);
myApplist.setAdapter(applistAdapter);
myApplist.setOnItemClickListener(this);
}
The problem which I have with this code is, that it alway populates only one Fragment with the applist. On first start the first fragment is populated with the apps but if you swipe two fragments to right and then one back to left, this fragment (the second) is populated. The impression which I have is that the
return new ApplistFragment();
from the ViewPager does actually not create an independent instance of the fragment. (At least I think so)
What I want is to show an undefined amount of Fragments which can all show different categories of the same list. So if possible I want to load the list only once and I want to reuse the Fragment code for every fragment since I don't want to restrict the max amount of categories.
My idea was to use the ApplistAdapter to filter the unwanted apps for every category but I really don't know.
Help is really really appreciated since I have no idea how to go along.
Thanks in advance.
I would change how this works all together. A few suggestions of the top of my head:
Download the data in the Service instead of the Activity and
persist it somewhere for example in a database.
You can use an Intent to tell the Service what you want to download and when to do it.
You can also use an IntentService instead of a Service.
IntentServices already handle each Intent in a separate worker
thread so you don't need an AsyncTask or anything like that in the
IntentService to perform the downloading.
Each Fragment should load the data from the database in onResume(). You
can use local broadcasts to inform the Fragments when the data
changed while they are being displayed.
If you don't give us more information it will be difficult to give you very specific advice.

Is it possible to have a fragment without an activity?

This is unlikely but it would potentially save me a lot of time to re-write the same code.
I want to implement a UI using alert-type service (like Chathead) yet I'd still like to use my fragments. Is it possible? I know I can add views to the window but fragments?
Fragments are part of the activity, so they cannot replace activity. Though they behave like activity, they cannot stand themselves. Its like view cannot itself act like activity.
From Android Developers:
A Fragment represents a behavior or a portion of user interface in an
Activity. You can combine multiple fragments in a single activity to
build a multi-pane UI and reuse a fragment in multiple activities. You
can think of a fragment as a modular section of an activity, which has
its own lifecycle, receives its own input events, and which you can
add or remove while the activity is running (sort of like a "sub
activity" that you can reuse in different activities).
I hope it is helpful to you.
Well as people have pointed out you can't, but, you can always create
some sort of fragment wrapper.
For example purposes:
public class ActivityFragmentWrapper extends FragmentActivity {
public static final String KEY_FRAGMENT_CLASS = "keyFragmentClass";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() != null) {
String fragmentClass = (String) getIntent().getExtras().get(KEY_FRAGMENT_CLASS);
try {
Class<?> cls = Class.forName(fragmentClass);
Constructor<?> constructor = cls.getConstructor();
Fragment fragment = (Fragment) constructor.newInstance();
// do some managing or add fragment to activity
getFragmentManager().beginTransaction().add(fragment, "bla").commit();
} catch (Exception LetsHopeWeCanIgnoreThis) {
}
}
}
public static void startActivityWithFragment(Context context, String classPathName) {
Intent intent = new Intent(context, ActivityFragmentWrapper.class);
intent.putExtra(KEY_FRAGMENT_CLASS, classPathName);
context.startActivity(intent);
}
}
You can start it like:
ActivityFragmentWrapper.startActivityWithFragment(context, SomeSpecificFragment.class.getCanonicalName().toString());
Of course if your fragment has another constructor you have to retrieve different
one, but that part gets easier.
No, Fragments can't exist without an Activity. They need an activity for their entry point otherwise they can't initiate their UI components and their lifecycle can't go beyond onAttach and onCreateView

Handling onNewIntent in Fragment

I am writing an application that uses NFC to read some data stored on it. My application uses Fragments and Fragment don't come with onNewIntent() method. Since, the data I am reading is done with my separate class which handles NFC related operation, the only thing I need to do is update the TextView inside the Fragment. However this implementation can also be used to pass new Intent to the Fragment.
Here is my current implementation which makes use of an interface. I am calling the listener after new Intent is received and NFC related checks succeeds. This is the FragmentActivity which hosts Fragment.
public class Main extends FragmentActivity implements
ActionBar.OnNavigationListener {
private Bundle myBalanceBundle;
private NFC nfcObj;
private NewBalanceListener newBlanceListener;
#Override
public void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onResume() {
getNFCState();
super.onResume();
}
private void getNFCState() {
//Other NFC related codes
else if (nfc_state == NFC.NFC_STATE_ENABLED){
readNFCTag();
}
}
private void readNFCTag() {
//Other NFC related codes
if (getIntent().getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
nfcObj.setTag((Tag) getIntent().getParcelableExtra(
NfcAdapter.EXTRA_TAG));
nfcObj.readQuickBalance();
transitQuickReadFragment(nfcObj.getCurrentBalance());
}
}
private void transitQuickReadFragment(String balance) {
// Creates a balance bundle and calls to select MyBalance Fragment if it
// is not visible. Calls listener is it is already visible.
if (actionBar.getSelectedNavigationIndex() != 1) {
if (myBalanceBundle == null)
myBalanceBundle = new Bundle();
myBalanceBundle.putString(Keys.BALANCE.toString(), balance);
actionBar.setSelectedNavigationItem(1);
} else {
newBlanceListener.onNewBalanceRead(balance);
}
}
#Override
public boolean onNavigationItemSelected(int position, long id) {
// Other fragment related codes
fragment = new MyBalance();
fragment.setArguments(myBalanceBundle);
newBlanceListener = (NewBalanceListener) fragment;
// Other fragment related codes
}
// Interface callbacks. You can pass new Intent here if your application
// requires it.
public interface NewBalanceListener {
public void onNewBalanceRead(String newBalance);
}
}
This is MyBalance Fragment which has TextView that needs to be updated whenever NFC is read:
public class MyBalance extends Fragment implements NewBalanceListener {
private TextView mybalance_value;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//Other onCreateView related code
Bundle bundle = this.getArguments();
if (bundle != null)
mybalance_value.setText(bundle.getString(Keys.BALANCE.toString(),
"0.00"));
else
mybalance_value.setText("0.00");
//Other onCreateView related code
}
#Override
public void onNewBalanceRead(String newBalance) {
mybalance_value.setText(newBalance);
}
}
This code works perfectly like expected for my application but, I want to know if there is better way to handle new Intent from Fragments?
This is an old question, but let me answer it in case anybody bumps into it.
First of all you have a bug in your code:
You can't register Fragments as listeners inside Activity the way you do it. The reason is that Activity and Fragments can be destroyed by the system and re-created later from saved state (see documentation on Recreating an Activity). When this happens, new instances of both the Activity and the Fragment will be created, but the code that sets the Fragment as a listener will not run, therefore onNewBalanceRead() will never be called. This is very common bug in Android applications.
In order to communicate events from Activity to Fragment I see at least two possible approaches:
Interface based:
There is an officially recommended approach for communication between Fragments. This approach is similar to what you do now in that it uses callback interfaces implemented by either Fragment or Activity, but its drawback is a tight coupling and lots of ugly code.
Event bus based:
The better approach (IMHO) is to make use of event bus - "master component" (Activity in your case) posts "update" events to event bus, whereas "slave component" (Fragment in your case) registers itself to event bus in onStart() (unregisters in onStop()) in order to receive these events. This is a cleaner approach which doesn't add any coupling between communicating components.
All my projects use Green Robot's EventBus, and I can't recommend it highly enough.
There is at least one alternative: From Activity.onNewIntent documentation:
An activity will always be paused before receiving a new intent, so you can count on onResume() being called after this method.
Note that getIntent() still returns the original Intent. You can use setIntent(Intent) to update it to this new Intent.
FragmentActivity.onNewIntent documentation is different but I don't think it contradicts the above statements. I also make the assumption that Fragment.onResume will be called after FragmentActivity.onResume, even though the documentation seems a little fussy to me, though my tests confirm this assumption. Based on this I updated the Intent in the activity like so (examples in Kotlin)
override fun onNewIntent(intent: Intent?) {
setIntent(intent)
super.onNewIntent(intent)
}
And in Fragment.onResume I could handle the new intent like so
override fun onResume() {
super.onResume()
doStuff(activity.intent)
}
This way the activity don't need to know about what fragments it holds.
No, there is no better way. Fragments can live longer than Activities and are not necessarily tied to them at all so providing new intents would not make sense.
Btw, you have a few bugs in your code :)
if (actionBar.getSelectedNavigationIndex() != 1) {
Magic numbers are bad! use a constant.
if (myBalanceBundle == null)
myBalanceBundle = new Bundle();
myBalanceBundle.putString(Keys.BALANCE.toString(), balance);
actionBar.setSelectedNavigationItem(1);
we already know that the navigationitem is set to 1
} else {
newBlanceListener.onNewBalanceRead(balance);
Add a null check. The user might have never selected a navigation item.

Categories

Resources