Although I got my code to work, I now have no idea what it's actually doing.
My app is an RSS reader, and the main content is in a Fragment containing a ListView with NewsStory objects. When a list item is clicked, it opens an Intent with the website linked from the RSS.
Now the problem is, I don't understand the Intent here, it's not the way I've ever used Intents before.
Also, I have to make it so that when I change the orientation, the original profile Fragment takes up the left half of the screen and the linked webpage takes up the right half of the screen. I've tinkered around with it, to no avail. I did a bit of research on orientation changes, but I feel like doing things with Fragments always changes how everything works. Anyway, here's the Fragment code. Any thoughts would be greatly appreciated.
public class HeadlineFragment extends Fragment {
EditText input;
Button search;
ListView headlines;
NewsDataSource ds;
public HeadlineFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_headline,container,false);
input = (EditText)v.findViewById(R.id.txtInput);
search = (Button)v.findViewById(R.id.btnSearch);
headlines = (ListView)v.findViewById(R.id.listView);
try {
ds = new NewsDataSource();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
headlines.setAdapter(new NewsDataSourceAdapter(this.getActivity(), ds));
headlines.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent,View view, int position, long id)
{
String url = NewsDataSource.stories[position].getLink();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
});
return v;
}
/*
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
}
*/
}
Firstly, regarding that Intent, its an implicit Intent. When you set the action as ACTION_VIEW and add a URI / URL as an extra, the OS gets the message that you want to open an app that can navigate to that URI / URL.
Secondly, for showing a two-pane layout in landscape mode, you'll have to show that RSS content in a Fragment instead of an Activity as you are currently doing, and you'll have to display those Fragments side-by-side in an Activity in landscape mode. See the Retrieving a List of Contacts example for a really good explanation of how to display a multi-pane master detail layout in portrait mode.
References:
1. Intents and Intent Filters.
2. Planning for Multiple Touchscreen Sizes.
Related
I'm new to SO and fairly new to coding, so please accept my apologies in advance if I break rules or expectations here.
I have an unusual setup involving two recyclerViews, which I'll explain here and also paste a simplified version of the code below (as there is so much not relevant to this question).
In what I'll call verticalRecyclerViewActivity, a verticalRecyclerViewAdapter is called, with data it fetches from Firebase and loads into arrayLists.
If the user clicks on an item in the vertical recyclerview, a new dialog fragment which I'll call horizontalRecyclerViewDialogFragment is inflated, and that loads what I'll call horizontalRecyclerView (which has similar items to the vertical one, in more detail, with options to click on them to review them).
If the user clicks on an item in the horizontalRecyclerView, a new activity which I'll call reviewItem is started (through an Intent). When the user submits their review, it finishes and returns (through the backstack) to the horizontal RecyclerView. That can also happen if they press the back button without actually submitting a review. That all works fine, but I need the horizontalRecyclerView to show that they have (or haven't) reviewed the item and state the score they gave it in a review.
Calling notifyDataSetChanged won't work for this because of how information comes through two recyclerViews and Firebase calls (or, at least, it would be very inefficient).
I've tried using startActivityForResult (I know it's deprecated, but if I could get that to work I could try using the newer equivalent which I don't yet understand) but the problem is that the result is returned to the original (VerticalRecylcerView) activity, which is two recyclerView adapters and one fragment beneath what needs to be updated, and I don't know how to pass that data to the horizontal Recyclerview.
I've also tried using interfaces but was unable to pass it through the Intent (tried using Parcelable and Serializable, but it seems neither can work in this situation?).
Since the review is updated on Firebase, I could have the horizontal Recyclerview listen for a change, but that seems very inefficient?
So I've found a solution using localBroadcast (which I know is also deprecated). The Intent (with the review score) is transmitted when it is reviewed and received in the horizontal recyclerView adapter. But when and how should I unregister the adapter? Ideally the receiver would be turned on when the user goes to the Review activity and turned off once the user returns from that activity and the (horizontal) recyclerView holder is updated, whether the review is successfully submitted or whether the user just presses the back button and never submits a review.
My question is similar to this one: How to unregister and register BroadcastReceiver from another class?
That is noted as a duplicate of this one: How to unregister and register BroadcastReceiver from another class?
There's a lot in those questions I don't understand, but the important difference I think between their and my cases is that I would just like the receiver to know when a review is submitted, and ideally be unregistered then, or possibly when the viewHolder is recycled, which I tried but also didn't work since it's not connected to the viewHolder (should it be?).
Any help would be much appreciated, thank you!
public class verticalRecyclerViewActivity extends AppCompatActivity {
// Loads an XML file and assembles an array from Firebase.
mVerticalRecyclerView = findViewById(R.id.verticalRecyclerView);
verticalRecyclerViewAdaptor mVerticalRecyclerViewAdaptor = new verticalRecyclerViewAdaptor (this); // also pass other information it needs
mVerticalRecyclerView .setAdapter(mVerticalRecyclerViewAdaptor);
}
public class verticalRecyclerViewAdaptor extends RecyclerView.Adapter<verticalRecyclerViewAdaptor.singleHolder> {
// Usual recyclerView content
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
horizontalRecyclerViewFragment mHorizontalRecyclerViewFragment = new horizontalRecyclerViewFragment();
// lots of arguments passed it needs.
FragmentManager fragmentManager = ((FragmentActivity) view.getContext()).getSupportFragmentManager();
mHorizontalRecyclerViewFragment.show(fragmentManager, null);
}
public class mHorizontalRecyclerViewFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity().getApplicationContext(); // Not sure why I need this, but it works.
View horizontalRecyclerViewView = getActivity().getLayoutInflater().inflate(R.layout.horizontal_recyclerview_holder, new CardView(getActivity()), false);
Dialog horizontalRecyclerViewDialog = new Dialog(getActivity());
horizontalRecyclerViewDialog.setContentView(horizontalRecyclerViewView);
mHorizontalRecyclerView = horizontalRecyclerViewView.getRootView().findViewById(R.id.horizontalRecyclerView);
mHorizontalRecyclerViewAdapter = new horizontalRecyclerViewAdapter (mContext)
// Other arguments passed
mHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(),
LinearLayoutManager.HORIZONTAL, false));
mHorizontalRecyclerView.setAdapter(mHorizontalRecyclerViewAdapter);
}
public class horizontalRecyclerViewAdapter extends RecyclerView.Adapter<horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder> {
public horizontalRecyclerViewAdapter(){}
// Blank constructor and also one with lots of arguments for it to work.
public horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.horizontal_recyclerview_adaptor_holder, parent, false);
return new horizontalRecyclerViewAdapter.horizontalRecyclerViewHolder(view);
}
public void onBindViewHolder(#NonNull horizontalRecyclerViewHolder holder, int position) {
// Connect up various views.
holder.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
LocalBroadcastManager.getInstance(mContext).registerReceiver(reviewSubmittedListener, new IntentFilter("reviewSubmitted"));
Intent reviewNow = new Intent(view.getContext(), ReviewActivity.class);
// Put extra details with the intent
view.getContext().startActivity(reviewNow);
}
BroadcastReceiver reviewSubmittedListener = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent reviewFinishedIntent) {
int reviewScore = reviewFinishedIntent.getExtras().getInt("reviewScore");
// Update the horizontal RecyclerView with the information received from the review Activity.
}
};
}
public class ReviewActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_review_item);
// Set up the review, using Firebase and data passed through the intent.
}
public void submitReview() {
// Check that the review is complete/valid and submit it through Firebase
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(ReviewItemActivity.this);
Intent reviewFinishedIntent = new Intent("reviewSubmitted");
reviewFinishedIntent.putExtra("reviewScore", overallScore);
LocalBroadcastManager.getInstance(this).sendBroadcast(reviewFinishedIntent);
finish();
}
If you are using RxJava you can use the RxBus else you can use one of many EventBus implementation for this.
If that is not the path you want to take then you can have a shared view model object that can be used only for communication between fragments see this article.
I'm new to android studio and dont fully understand the working of it yet but I've been attempting to send data from one fragment to another in android studio. I've not been able to find a clear answer to this issue. What I'm trying to do is make a basic parking app where a qr code is scanned and the data that is in the qr code (parking name and price per hour) will be send to the next fragment since the qr code scanner fragment will open an addParkingSession fragment where I'll need the parking data. I've been trying to use "Navigation" to do this but i'm unable to find a way to send data via this methode. Is there a different mehode that better suits this (it has to go from fragment to fragment though) I've been trying to use intent but navigation doesn't seem to have an option to send an intent along with it.
Here is some of my code as an example. Thank you for your help and understanding
codeScanner.setDecodeCallback(new DecodeCallback() {
#Override
public void onDecoded(#NonNull Result result) {
getActivity().runOnUiThread(new Runnable(){
#Override
public void run(){
if(result.getText() != null) {
String[] parkingdata = resultData.toString().split(",");
Intent intent = new Intent(getActivity().getBaseContext(), AddSession.class);
intent.putExtra("parkingName", parkingdata[0]);
intent.putExtra("parkingPrice", parkingdata[1]);
Navigation.findNavController(view).navigate(R.id.action_qrSession_to_addSession);
}
}
});
}
});
You can pass a bundle object as the second argument in .navigate() and access it in your fragment with getArguments().
final Bundle bundle = new Bundle();
bundle.putString("test", "Hello World!");
Navigation.findNavController(view).navigate(R.id.action_qrSession_to_addSession, bundle);
public class MyFragment extends Fragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// the string you passed in .navigate()
final String text = getArguments().getString("test");
}
}
I have a FrameLayout and put there some fragments by click on button, next click should remove fragment from FrameLayout, I do this by removeAllViews() (FrameLayout is in another Fragment so translaction method is in Activity).
I need to do some action when removeAllViews() starts and have to do it in Fragment class but something goes wrong.
I tried:
OnDestroy()
OnDestroyView()
OnPause()
in Fragment class
but it works like:
put Fragment in FrameLayout (from Activity)
use removeAllViews() (from Activity)
there is no Fragment in FrameLayout (is clear) but nothing else happens and methods are not working
put new Fragment in FrameLayout (from Activity) - now all methods (OnDestroy() from Fragment class) works (probably it's real time to destroy old fragment)
How is it possible to 'get moment' when Fragment is not exists for user? I want to send some information to server if user hides Fragment.
#Edit3
code from method from Activity where I want to make translaction
public void showProductsList(String productType,int containerID){
List<String> prodNames = new ArrayList<String>();
List<Long> prodIds = new ArrayList<Long>();
DatabaseDAOProdProtein dao = new DatabaseDAOProdProtein(getApplicationContext());
dao.open();
List<DatabaseProduct> productList = dao.getAllProducts();
for(int i=0;i<productList.size();i++){
prodNames.add(productList.get(i).getName());
prodIds.add(productList.get(i).getId());
}
dao.close();
ProductsList productsList = new ProductsList(productType,prodNames,prodIds);
productsList.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
// Note that system bars will only be "visible" if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
Toast.makeText(getApplicationContext(),"action1 " ,Toast.LENGTH_LONG).show();
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// TODO: The system bars are visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
Toast.makeText(getApplicationContext(),"action2 " ,Toast.LENGTH_LONG).show();
} else {
// TODO: The system bars are NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
Toast.makeText(getApplicationContext(),"action3 " ,Toast.LENGTH_LONG).show();
}
}
});
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(containerID, productsList).commit();
}
I used this method in another Fragment by:
((MainActivity) getContext()).showProductsList("carb", carbContainer.getId());
there is an error:
Error:(560, 21) error: cannot find symbol method setOnSystemUiVisibilityChangeListener(<anonymous OnSystemUiVisibilityChangeListener>)
You say:
"How is it possible to 'get moment' when Fragment is not exists for
user? I want to send some information to server if user hides
Fragment."
I now know you did not mean "hide", so just use the OnDestroy() method.
Try this to trigger the "hide"
View topLevelLayout = findViewById(R.id.top_layout);
topLevelLayout.setVisibility(View.INVISIBLE);
You cannot go into stopped state while Fragment (Activity) is visible. Android destroying activities, killing processes
The best way to make sure something runs via a view is to run it via a post:
topLevelLayout.post(new Runnable()
{
#Override
public void run()
{
topLevelLayout.removeAllViews();
}
}
To get notified of system UI visibility changes, register an View.OnSystemUiVisibilityChangeListener to your view (fragment).
https://developer.android.com/training/system-ui/visibility.html
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(getContext(),"action0 " ,Toast.LENGTH_LONG).show();
Fragment your_frag = new ProductsList(productType,prodNames,prodIds);
getSupportFragmentManager().beginTransaction().replace(containerID,your_frag).commit();
getSupportFragmentManager().executePendingTransactions();//make sure onCreateView has executed
your_frag.getRootView().setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
// Note that system bars will only be "visible" if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
Toast.makeText(getContext(),"action1 " ,Toast.LENGTH_LONG).show();
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// TODO: The system bars are visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
Toast.makeText(getContext(),"action2 " ,Toast.LENGTH_LONG).show();
} else {
// TODO: The system bars are NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
Toast.makeText(getContext(),"action3 " ,Toast.LENGTH_LONG).show();
}
}
});
}
A typical fragment looks like this:
public class HomeFragment extends Fragment {
View mRootView = null;
public HomeFragment(){}//null constructor
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_home, container, false);
return mRootView ;
}
public View getRootView ()
{
return mRootView;
}
}
I've found a ton of conflicting information regarding the proper way to restore application state when using Fragments embedded in Activities. Please let me know if my architecture is the problem because that is totally possible. My test Weather app is architected as follows.
The main activity "ReportsActivity" contains the fragment "ReportsFragment" (This is a list of the next 10 days of Weather Reports)
ReportsFragment has an onItemClickListener that launches a new Activity "WeatherDetailActivity" and passes it an intent which contains some JSON Data that I use to populate the Weather Detail UI. This data is then presented on a fragment that the WeatherDetailActivity manages.
My problem is, when the user presses the back button, the ReportsFragment has been destroyed so it runs through its full lifecycle. I've tried a number of techniques I've found online to load the activity's data from a bundle, but no matter what I've tried so far the Intents' Extras are null in the ReportsActivity's onCreate method. (Note: the reason I need to do this is to avoid firing off an API Call each time I open my main Activity which fetches weather data from Weather Underground).
I'm struggling determining what would be the best way to construct this app: Should I have a single activity that pushes and pops Fragments that it manages? Or are multiple activities that each manage their own fragments the standard practice?
At the moment here is how I'm attempting to save my application state onto the intent. I'm trying to save the state in onPostExecute from my AsyncTask so i'm on the main thread after i've fetched my results from the API Call:
#Override
protected void onPostExecute(Report[] result){
if (result != null){
ArrayList<String>reportsArrayList = new ArrayList<String>();
Gson jsonArray = new GsonBuilder().setPrettyPrinting().create();
for (int x = 0; x < result.length; x++){
reportsArrayList.add(jsonArray.toJson(result[x], Report.class));
}
mExtras.putStringArrayList(ReportsActivity.ReportsActivityState.KEY_ACTIVITY_REPORTS,reportsArrayList);
}
}
I then attempt to restore state from the ReportsActivity's onCreate Method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reports);
if (savedInstanceState == null) {
Intent intent = getIntent();
mFragment = ReportsFragment.newInstance(intent
.getStringArrayListExtra(ReportsActivityState.KEY_ACTIVITY_REPORTS));
getFragmentManager().beginTransaction()
.add(R.id.container, mFragment).commit();
}
}
In all cases the StringArrayListExtra I'm trying to get from the intent return null.
This could very well be me trying to solve an Android problem with an iOS mindset, but is there not an easy way to just restore the main activity to what it was before I pushed the detail view?
I think it would be worth your while taking a look at EventBus.
Basically you can define a object holder of any kind, for example:
class WeatherData {
List<String> reports;
public WeatherData(List<String> reports) {
this.reports = reports;
}
}
Now, in an Activity or Fragment in which you wish to remember the state, or pass some state to another Activity or Fragment do:
// this removes all the hazzle of creating bundles etc
EventBus.getDefault().postSticky(new WeatherData(reports));
And any where in your code your wish to know the most recent WeatherData:
WeatherData weatherData = EventBus.getDefault().getSticky(WeatherData.class);
EventBus also has nice methods for event handling (button clicks, completion of long running processes, etc..)
The library can be found here: https://github.com/greenrobot/EventBus
And some more examples here: http://awalkingcity.com/blog/2013/02/26/productive-android-eventbus/
Some suggestions without using 3. part library:
1) Calling setRetainInstance(true) in your fragments onCreate method, what this should do is to persist public variables between instances.
Though it seems it does not work for fragments on the back stack: Understanding Fragment's setRetainInstance(boolean)
2) Hand the fragment data to your Activity, something like reading/updating ((YourActivity)getActivity()).someFragmentBundle, possibly save it in onSaveInstanceState of the Activity and retrieve it in onCreate. That is, having your Activity hold the data in-between instances.
3) You could also persist the data, saving it to a file or using SharedPreferences http://developer.android.com/training/basics/data-storage/index.html
This method has the advantage that it will enable restoring the data even after a complete kill of your app.
The Architectural question
Disclaimer: subjective opinion
I would generally say keep the Activity as 'slim' as possible, holding a range of related fragments.
Thus, having multiple Activities is fine but they should each manage a set of (or a single) related fragments that are relevant for the current Activity.
It just occurred to me to check out one of the Android Studio templates that Google Provides that I often overlooked. From Google's own templates, it appears clear that the preferred method for Master Detail Activities/Fragments is to have each Fragment Managed by their own activities (as I was attempting to achieve above).
(I should note that I was able to successfully achieve to flow that I wanted using a single Activitiy with multiple fragments and customizing the Animation and forcefully showing and hiding the up button.)
PersonListActivity.java
public class PersonListActivity extends Activity
implements PersonListFragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person_list);
if (findViewById(R.id.person_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((PersonListFragment) getFragmentManager()
.findFragmentById(R.id.person_list))
.setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
/**
* Callback method from {#link PersonListFragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(PersonDetailFragment.ARG_ITEM_ID, id);
PersonDetailFragment fragment = new PersonDetailFragment();
fragment.setArguments(arguments);
getFragmentManager().beginTransaction()
.replace(R.id.person_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, PersonDetailActivity.class);
detailIntent.putExtra(PersonDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
}
PersonListFragment.java
public class PersonListFragment extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
/**
* A dummy implementation of the {#link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PersonListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: replace with a real list adapter.
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
DummyContent.ITEMS));
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
}
PersonDetailActivity.java
public class PersonDetailActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person_detail);
// Show the Up button in the action bar.
getActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(PersonDetailFragment.ARG_ITEM_ID,
getIntent().getStringExtra(PersonDetailFragment.ARG_ITEM_ID));
PersonDetailFragment fragment = new PersonDetailFragment();
fragment.setArguments(arguments);
getFragmentManager().beginTransaction()
.add(R.id.person_detail_container, fragment)
.commit();
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
navigateUpTo(new Intent(this, PersonListActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
PersonDetailFragment.java
public class PersonDetailFragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
/**
* The dummy content this fragment is presenting.
*/
private DummyContent.DummyItem mItem;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PersonDetailFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM_ID)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_person_detail, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
((TextView) rootView.findViewById(R.id.person_detail)).setText(mItem.content);
}
return rootView;
}
}
I've been working on a ListView within Android with a number of items. I'd like each list item to have a corresponding HTML file which loads in a WebView when the list item is clicked. Every list item will have a corresponsing HTML file. The HTML are stored on the device.
To give an idea of the ListView I'm working with I've modified the tutorial here http://javatechig.com/android/android-listview-tutorial/.
I think my problem is I'm used to iOS dev and I'm having a hard time getting my head round it without thinking how I would tackle it in iOS!
Any input appreciated,
Thanks.
One way to do this in iOS is to use the storyboard template with the list and detail views.
You could do the same in Android with two different activities. You already have one activity with the list. You should add a second activity with just a WebView (+ whatever navigation you need surrounding it).
To wire it up you add an OnItemClickListener to the listview that fires an intent with the proper activity action.
// logic to get the html file goes here
Intent i = new Intent(context, MyWebViewActivity.class);
i.putExtra("fileToShow", theFile);
context.startActivity(i);
In your MyWebViewActivity you can do this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_with_webview);
final Intent intent = getIntent();
if (null != intent) {
if (intent.hasExtra("fileToShow")) {
WebView myWebView = (WebView) findViewById(R.id.my_web_view);
myWebView.loadUrl("file:///" + intent.getExtras().getString("fileToShow");
}
}
}
You might need to add some more code and layout around this to fit your specific need - but this general approach should work out fine.
you can use setOnItemClickListener for listview and pass the html to loadUrl like...
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> element, View arg1, int pos, long arg3){
// TODO Auto-generated method stub
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(htmladdress);
}
});
Create a new activity for the detail view, containing the webview
then, in your listview (master view) onItemClickListener callback get the uri of the file, bundle it as a intent extra and start a new activity
The activity receives the intent, extracts the uri from the extras, and has webview load it
the local uris are like this "file:///" + context.getExternalFilesDir(null) + "/..."
so you probably want to set up a database to store the uris, and use a cursor adapter with the listview