SetShareIntent when a new fragment is displayed - android

My question is similar to Android setShareIntent within fragment, but I read the answers there and couldn't figure out how to apply them to my situation.
Quick summary of my question
I'd like to setShareIntent each time the fragment changes, i.e. whenever a new fragment is shown to the user. How can I do that? Where should the setShareIntent call go?
Longer version with code snippets
Here's a skeleton of my code:
import android.support.v7.widget.ShareActionProvider;
public class SolvePuzzle extends ActionBarActivity {
private ShareActionProvider mShareActionProvider;
static AppSectionsPagerAdapter mAppSectionsPagerAdapter;
static ViewPager mViewPager;
...
public void onCreate(Bundle savedInstanceState) {
...
mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mAppSectionsPagerAdapter);
}
}
Then comes
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.solve_puzzle, menu);
MenuItem item = menu.findItem(R.id.menu_item_share);
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
if (mShareActionProvider == null) {
// Following https://stackoverflow.com/questions/19358510/why-menuitemcompat-getactionprovider-returns-null
mShareActionProvider = new ShareActionProvider(this);
MenuItemCompat.setActionProvider(item, mShareActionProvider);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, "Some text"); // For debugging
shareIntent.setType("text/plain");
setShareIntent(shareIntent);
Log.d(DEBUG_TAG, "Called new mShareActionProvider(this) and set share intent");
}
return true;
}
private void setShareIntent(Intent shareIntent) {
// Want to call this whenever new puzzle fragment is displayed
if (mShareActionProvider != null) {
mShareActionProvider.setShareIntent(shareIntent);
} else {
Log.d(DEBUG_TAG, "Could not set share intent: mShareActionProvider == null");
}
}
I then have a public class AppSectionsPagerAdapter extends FragmentPagerAdapter and a public class SolvePuzzleFragment extends Fragment implements OnClickListener.
Currently I call setShareIntent in the onCreateView of SolvePuzzleFragment.
That doesn't work: when I look at my logcat, I see that the setShareIntent call coming from the fragments (first two lines) happens before the onCreateOptionsMenu call from the SolvePuzzle class (last line):
12-12 12:39:26.505: D/Debug(3864): Could not set share intent:
mShareActionProvider == null
12-12 12:39:26.515: D/Debug(3864): Could not set share intent:
mShareActionProvider == null
12-12 12:39:26.565: D/Debug(3864): Called new
mShareActionProvider(this) and set share intent
...and it looks like there are two calls from the fragments. Is that because both the current fragment and the next one in line are created (have their onCreateView called), even though only the first one is being displayed?
It looks like calling setShareIntent in the fragment's onCreateView is a mistake. What I want to do is call setShareIntent when a new fragment is displayed to the user. How do I do that?
Edit: Additional information:
The share button currently works, but it sends the "Some text" message that I set for debugging purposes in onCreateOptionsMenu. I'd like that intent to be overwritten by a fragment-related intent as soon as the first fragment is displayed to the user (and overwritten each time a new fragment is displayed).

How can I do that? Where should the setShareIntent call go?
Register an OnPageChangeListener with your ViewPager via setOnPageChangeListener() and put your setShareIntent() call in onPageSelected() of the listener.

Related

How do I troubleshoot an NPE when creating my activity in android?

I'm getting an NPE when I start an activity in my application. It doesn't happen right away when I boot my phone and start debugging on it. After several dozen new builds are pushed to my phone it eventually starts crashing with this error every single time. I can remedy it temporarily by opening a few other activities first before I activate the errant one.
Any ideas what could cause this? It's a somewhat complicated Activity with a DrawerLayout, and a fragment that contains a SwipeRefreshLayout with a ListView.
Exception
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to read from field 'boolean android.support.v4.app.BackStackRecord.mAddToBackStack' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
Activity Code
public class TastingsActivity extends BaseActivity implements TastingListFragment.Callbacks {
public static final String TAG = TastingsActivity.class.getSimpleName();
private TastingListFragment mTastingListFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tastings);
//create fragment
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
TastingListFragment fragment = new TastingListFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment).commit();
}
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_TASTINGS;
}
//================================================================================
// Handlers
//================================================================================
#Override
public void onTastingSelected(Tasting tasting) {
Intent intent = new Intent(this, TastingActivity.class);
intent.putExtra(TastingDetailsFragment.EXTRA_TASTING_ID, tasting.getId());
startActivity(intent);
}
}

How can I pass a Share intent off to my fragment?

I'm experimenting with receiving Share intents, but I've come across a situation that I can't wrap my head around. Currently my app shows up as a Share option in other apps, and my Activity receives such these intents just fine. However, I'm having a problem with passing off this intent to a Fragment that I want to handle it. Namely, the Fragment isn't being loaded in time for my Activity to pass it off.
Here's what I have so far in my Activity's onCreate():
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_screen);
if (savedInstanceState == null)
{
Log.i(TAG, "Loading new fragment");
getFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment(), "tag")
.commit();
}
PlaceholderFragment frag = (PlaceholderFragment) getFragmentManager().findFragmentByTag("tag");
Log.i(TAG, "Fragment Exists: " + (frag != null));
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if(Intent.ACTION_SEND.equals(action) && type != null)
{
if("text/plain".equals(type))
{
//handleIntent(intent); // Doesn't do anything right now
}
}
}
And here's the log output from when I hit Share in another app and tell it to send the text to my app:
I/ActivityStartScreen﹕ Loading new fragment
I/ActivityStartScreen﹕ Fragment Exists: false
I/ActivityStartScreen﹕ Handling Intent
I/PlaceholderFragment﹕ Loading Fragment View
As you can see, the Activity wants to hand off the intent before the Fragment is ready, even though I've added a Fragment to the layout before handling the intent. Is part of the problem the fact that the FragmentTransaction needs time to commit? That's the only reason I can imagine the null-check would fail...
What do I do in this situation so that I can pass off the intent to the Fragment once it's ready to go?

update fragment ui based off of Activity's Broadcast Receiver

First let me say I have read through many of the questions related to fragments on SO. However, I can't seem to find a situation quite the same as mine.
I have myActivity that is using the PageAdapter, each page being a fragment. I also have a service that receives updates about the network connections etc. The service triggers the receiver in myActivity. myActivity needs to update FragmentPage1 but because I am using a pageAdapter and creating my fragments at run time I cannot 'findFragmentbyId' etc. I do not need to pass any data I just need to trigger the function inside of the FragmentPage1 class. Please see code snippet below.
public class myActivity extends FragmentActivity implements ViewPager.OnPageChangeListener {
FragmentManager fm = getSupportFragmentManager();
mPagerAdapter = new PagerAdapter(fm);
mPager.setAdapter(mPagerAdapter);
mPager.setOnPageChangeListener(this);
// add tabs. Use ActionBar for 3.0 and above, otherwise use TabWidget
final ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.addTab(bar.newTab()
.setText(R.string.fragment_page_1)
.setTabListener(new ActionBarTabListener(mPager)));
bar.addTab(bar.newTab()
.setText(R.string.fragment_page_2)
.setTabListener(new ActionBarTabListener(mPager)));
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive (Context context, Intent intent){
if(intent.getAction().equals(InterfaceManager.BROADCAST_UPDATE_CONNECTION_STATS)) {
updateFragmentPage2();
} else if (intent.getAction().equals(InterfaceManager.BROADCAST_UPDATE_RULES)) {
UpdateFragmentPage1();
}
}
};
}
public class FragmentPage2 extends Fragment implements OnCheckedChangeListener, OnClickListener {
public void UpdateFragmentPage2() {
//update list view
}
}
Based on your code, Here's what you can do quickly.
int tabIndex = 0;
MyCustomFragment frag = getFragmentManager().findFragmentByTag(getActionBar().getTabAt(tabIndex).getText().toString());
frag.updateFragmentContent();
Create a custom base fragment MyCustomFragment and have an abstract method updateFragmentContent(), then you'd just need to change the tab index and no special typecast
Please note, The above is a cleaner way to do it. With your existing code, you can still have two separate type cast and call two separate methods to update corresponding fragments.
Hope this helps.
Due to the complex communication between the BroadcastReceiver, Fragment and Activity, I faced a similar problem and chose to slip out of that twist, and used the following:
When the BroadcastReceiver onReceive() method gets called add a boolean to the SharedPreferences as an indication flag that the Fragment should do something, and in the fragments onResume() method do the needed logic based on the SharedPreferences boolean set in the BroadcastReceiver onReceive() method.
Be informed though that there are better practices, and that I did not test such an approach on a long running term application.

How do you turn off share history when using ShareActionProvider?

The new ShareActionProvider available in Android 4.0 (or in earlier versions if you're using ActionBarSherlock) has a feature where the last used item is displayed in the action bar. Is there anyway to turn this off?
For me, the best solution for avoid the history icon is don't use ShareActionProvider, instead of it, create it as any other action:
<item
android:id="#+id/menu_action_share"
android:showAsAction="ifRoom"
android:icon="#drawable/ic_action_share"
android:title="#string/share"/>
at the menu/activity_actions.xml put a item with the ic_action_share icon...
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_actions, menu);
return super.onCreateOptionsMenu(menu);
}
Inflate the menu normally...
private void actionShare(){
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_SUBJECT, "my string");
i.putExtra(Intent.EXTRA_TEXT, "another string");
startActivity(i);
//Or like above will always display the chooser
//startActivity(Intent.createChooser(i, getResources().getText(R.string.share)));
}
Create a method with an ACTION_SEND intent
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_share:
actionShare();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
And finally call to this method from onOptionsItemSelected
more info ->Sending Simple Data to Other Apps
Start the share activity by yourself:
shareActionProvider.setShareIntent(intent);
shareActionProvider.setOnShareTargetSelectedListener(this);
#Override
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
// start activity ourself to prevent search history
context.startActivity(intent);
return true;
}
Then the ShareActionProvider will not add the chosen activity to the share history.
I created my own version of the ShareActionProvider (and supporting classes), you can copy them into your project (https://gist.github.com/saulpower/10557956). This not only adds the ability to turn off history, but also to filter the apps you would like to share with (if you know the package name).
private final String[] INTENT_FILTER = new String[] {
"com.twitter.android",
"com.facebook.katana"
};
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.journal_entry_menu, menu);
// Set up ShareActionProvider's default share intent
MenuItem shareItem = menu.findItem(R.id.action_share);
if (shareItem instanceof SupportMenuItem) {
mShareActionProvider = new ShareActionProvider(this);
mShareActionProvider.setShareIntent(ShareUtils.share(mJournalEntry));
mShareActionProvider.setIntentFilter(Arrays.asList(INTENT_FILTER));
mShareActionProvider.setShowHistory(false);
((SupportMenuItem) shareItem).setSupportActionProvider(mShareActionProvider);
}
return super.onCreateOptionsMenu(menu);
}
There is no API to do this. However, the class is really simple and you could very easily create your own version of ShareActionProvider that did not keep a history. You would just have to determine the sort order of the possible targets using some other means of ordering (e.g., alphabetically).
Point of clarification: It's not the "last used", it's "most often used", across a sliding window period of time.
If you prefer not to use history, then before creating your views, call
yourShareActionProvider.setShareHistoryFileName(null);
Description of this method, from the official docs (emphasis mine):
Sets the file name of a file for persisting the share history which history will be used for ordering share targets. This file will be used for all view created by onCreateActionView(). Defaults to DEFAULT_SHARE_HISTORY_FILE_NAME. Set to null if share history should not be persisted between sessions.
EDIT: I should clarify — The "most often used" item won't show up if there's no history, so this is currently the only way of removing that button.
Although It's been 2 years ago today but I'd like to share my experience as I made a custom ShareActionProvider class and add this line chooserView.getDataModel().setHistoryMaxSize(0); inside onCreateActionView() which did all the magic for me ! .. I tested it on Lollipop device and on API 16 emulator device and it works perfectly. here is my custom class :
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v7.internal.widget.ActivityChooserView;
import android.support.v7.widget.ShareActionProvider;
import android.view.View;
public class MyShareActionProvider extends ShareActionProvider {
/**
* Creates a new instance.
*
* #param context Context for accessing resources.
*/
public MyShareActionProvider(Context context) {
super(context);
}
#Override
public View onCreateActionView() {
ActivityChooserView chooserView = (ActivityChooserView) super.onCreateActionView();
Drawable icon;
if (Build.VERSION.SDK_INT >= 21) {
icon = getContext().getDrawable(R.drawable.share_icon);
}else{
icon = getContext().getResources().getDrawable(R.drawable.share_icon);
}
chooserView.setExpandActivityOverflowButtonDrawable(icon);
chooserView.getDataModel().setHistoryMaxSize(0);
return chooserView;
}
}
add the code like this:
private void addShareSelectedListener() {
if (null == mShareActionProvider) return;
OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() {
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
mContex.startActivity(intent);
return true;
}
};
//Set to null if share history should not be persisted between sessions.
mShareActionProvider.setShareHistoryFileName(null);
mShareActionProvider.setOnShareTargetSelectedListener(listener);
}
getSupportMenuInflater().inflate(R.menu.share_action_provider, menu);
// Set file with share history to the provider and set the share intent.
MenuItem actionItem = menu.findItem(R.id.menu_item_share_action_provider_action_bar);
ShareActionProvider actionProvider = (ShareActionProvider) actionItem.getActionProvider();
***actionProvider.setShareHistoryFileName(null);
OnShareTargetSelectedListener listener = new OnShareTargetSelectedListener() {
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
startActivity(intent);
return true;
}
};
actionProvider.setOnShareTargetSelectedListener(listener);***

Problem passing a Bundle with onSearchRequested

I'm actually trying to use the built-in search interface of Android, but I have some issues when I try to pass data with the search query.
Here is a brief explanation : I have an object in a first Activity (FirstActivity) called "Category" which implements Serializable (I already pass it successfuly between Activities) and I want to perform a search related to that category, and display the results in a second Activity (SecondActivity).
So, in FirstActivity I override the onSearchRequest method :
#Override
public boolean onSearchRequested() {
Bundle appData = new Bundle();
appData.putSerializable("category", _currentCategory);
Log.d(Utils.LOG_TAG, "Bundle : "+appData.keySet());
startSearch(null, false, appData, false);
return true;
}
And in SecondActivity, I try to get this Bundle :
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
handleIntent(getIntent());
}
private void handleIntent(Intent intent){
Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
if(appData == null) Log.d(Utils.LOG_TAG, "appData == null");
Log.d(Utils.LOG_TAG, "Extras : "+intent.getExtras().keySet());
}
Problem is that appData seems to be equals to null everytime. Here is the logcat output :
Bundle : [category]
appData == null
Extras : [query, user_query]
I tried to add some other objects into the Bundle (Booleans, etc...) but it doesn't change anything at all and I keep having a null appData.
I had problems figuring this out as well, and the examples I found didn't really help. A lot of them suggested overriding onSearchRequested(), but that actually doesn't work for SearchWidget. I ended up using the following (from danada) as a solution, since it seemed much simpler for me than setting up the OnQueryTextListener. I just overrode startActivity (in the first, search Activity) like so:
#Override
public void startActivity(Intent intent) {
//check if search intent
if(Intent.ACTION_SEARCH.equals(intent.getAction())) {
intent.putExtra("KEY", "VALUE");
}
super.startActivity(intent);
}
Then in the second, searchable Activity, I pulled out the info like so (called from onCreate() or from overriding onNewIntent() (if using singleTop)):
private void handleIntent(Intent intent){
if(Intent.ACTION_SEARCH.equals(intent.getAction())){
mSearchedQuery = intent.getStringExtra(SearchManager.QUERY);
mExtraData = intent.getStringExtra("KEY");
}
Simple, and worked like a charm! Check the link to the article above if you would like a little more explanation about it.
If you're using SearchView, it will not send your appData. Instead, consider using OnQueryTextListener. For example:
...
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.your-menu-id, menu);
/*
* Get the SearchView and set the searchable configuration.
*/
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.your-search-menuitem-id)
.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
/*
* Set query text listener here.
*/
searchView.setOnQueryTextListener(mSearchViewOnQueryTextListener);
return true;
}// onCreateOptionsMenu()
...
private final SearchView.OnQueryTextListener mSearchViewOnQueryTextListener = new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
/*
* You don't need to deal with "appData", because you already
* have the search query here.
*/
// Tell the SearchView that we handled the query.
return true;
}// onQueryTextSubmit()
#Override
public boolean onQueryTextChange(String newText) {
// TODO Auto-generated method stub
return false;
}// onQueryTextChange()
};// mSearchViewOnQueryTextListener
Note: You still need to keep the old way (using appData inside onSearchRequested()). In your onCreate(), if the extra for SearchManager.APP_DATA is null, that means you already handled the search query in the listener.
Conclusion:
If the SearchView is inactive, and you invoke it via onSearchRequested(), this will happen: onSearchRequested() >> onCreate() (ACTION_SEARCH contains SearchManager.APP_DATA).
If the SearchView is active, the user types and submits search, this will happen: SearchView.OnQueryTextListener.onQueryTextSubmit() >> onCreate() (ACTION_SEARCH without SearchManager.APP_DATA).
While putting data and retrieving it you are using two different keys. while putting you are using "category" and while retrieving you are using SearchManager.APP_DATA instead of using "category"
Try with
Bundle appData = intent.getBundleExtra("category");
Thanks
Deepak
In your example, you are asking for the keyset on the original intent object, and not the Bundle containing your appData. Here is an example that should work:
private void handleIntent(Intent intent){
final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
for (String key : appData.keySet()) {
Log.d(TAG, "key="+appData.getString(key));
}
}

Categories

Resources