How to pass data between 2 fragments displaying on the screen? - android

I have a Gridview filled with images, when you click in one of those images it starts a detais activity.
It's all working fine, but now I want to make a Master-Detail layout. So I created that "layout-land" folder and instead of only a gridview, it was this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background"
tools:context=".MainActivityFragment">
<GridView
android:id="#+id/main_grid"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:numColumns="auto_fit" />
<fragment
android:id="#+id/fragment"
android:name="com.example.lucas.popularmovies.DetailActivityFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
tools:layout="#layout/fragment_detail" />
</LinearLayout>
Before that, I was passing the Details' data as an Extra of the intent and retrieving it in the Fragment.
But when I'm displaying it all in the same screen, how do I pass the data in a way that will update the Details when I click in an image without starting a new activity? Thanks.

Simple.
DetailActivityFragment fragment= (DetailActivityFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
if (fragment != null) {
fragment.updateImage(url);
}

Since you have a static fragment in your layout which I assume you will not remove, I would say use a simple logic like this:
In your activity, create a method which will update your static fragment like:
public void updateImage(String imageUrl) {
DetailActivityFragment fragment= (DetailActivityFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
if (fragment != null) {
fragment.updateImage(imageUrl);
}
}
Whenever you click an image, you call this method.
And inside your fragment load your image in your ImageView(you should have one).

Use EventBus by square.
Create a Bus Object and register it in Activity
Bus bus = new Bus();
bus.register(this);
Create a public method in Activity with argument as your Model.
#Subscribe
public void getDataFromGrid(MainGridItem item) {
// TODO: React to the event somehow!
}
At GridView onItemClick post the item -
bus.post(mainGridItems.get(position));

You can find the fragment using
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragmentLoadingSpace)
if(fragment instanceof Fragment1){
((Fragment1) fragment).updateSelectedObjec(Objects object);
}

I posted a full answer on another SO link # Passing data between fragments contained in an activity. Simply find my user name and it is the only answer for that post. Let us know of your progress.

Related

Custom LinearLayout with EditText inside Fragment

You will understand my problem if you do it step by step as mentioned below (sorry, But I can't make you understand in other way). This might be confusing. But, I am sure if you read it once, you will understand.
Create a new android project, then create two Fragments. You will now
have
a. MainActivity.java.
b. FirstFragment(FirstFragment.java) with it's xml
file(first_fragment.xml).
c. SecondFragment(SecondFragment.java) with it's xml
file(second_fragment.xml).
Now create a custom linearLayout class (FormRow.java) as below:
public class FormRowNew extends LinearLayout {
public FormRowNew(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
View.inflate(getContext(), R.layout.form_row, this);
}
}
xml file (form_row) in this is like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="eeeee"/>
</LinearLayout>
Use this Custom linear class TWICE inside FirstFragment like below:
<gallery.com.yyyyyyyyy.FormRow
android:id="#+id/first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/txvFirst"
>
</gallery.com.yyyyyyyyy.FormRow>
<gallery.com.yyyyyyyyy.FormRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/first"
>
</gallery.com.yyyyyyyyy.FormRow>
REMEMBER: I have used it twice. You also use it AT LEAST TWICE.
Now, load FirstFragment in MainActivity.java at some id (rlRootLayout) like below.
FirstFragment firstFragment = new FirstFragment();
getSupportFragmentManager().beginTransaction().addToBackStack(null)
.replace(R.id.rlRootLayout, firstFragment).commit();
Similarly, load Second Fragment at same ID (rlRootLayout) from FirstFragment like below.
secondFragment blankFragment = new secondFragment();
getActivity().getSupportFragmentManager().beginTransaction()
.addToBackS tack(null).replace(R.id.rlRootLayout, blankFragment)
.commit();
Here i am adding SecondFragment to MainActivity from FirstFragment
at ID(rlRootLayout).
Problem :- Run the app and go to page as mentioned :
MainActivity ->
FirstFragment ->
Change something in BOTTOM FormRow(like add some text in EditText) ->
SecondFragment ->
FirstFragment.
Now, See the custom classes (FormRow) inside FirstFragment.
Whatever We had added in bottom FormRow class, automatically added in Top FormRow. I couldn't find the reason why is this happening. Even if I am doing nothing with top FormRow, it changes according to bottom when I come back from SecondFragment.
Please help.
That behavior is because of the id of the EditText. When the first fragment is inflating the layout while going back from the second fragment, the it assigns the values to edit text by using it id. So in this case both EditText's has the same id so it assigns the same value for the both Edittexts's
Save the text in editText while adding second fragment. Then set those saved text while going back to first from second this
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
//set edit text
}
is called.

Unable to pass data to fragment through Bundle

I'm learning android fundamentals and I came across this problem while creating my first app. I have an activity which passes on data to a fragment. The OnCreate method of the activity has a block like this:
if(savedInstanceState == null){
DetailActivityFragment detailFrag = DetailActivityFragment.newInstance(movieId);
getSupportFragmentManager().beginTransaction().add(android.R.id.content,detailFrag).commit();
}
setContentView(R.layout.activity_detail);
At the fragment (activity_detail) if I perform getParameters(), I receive null. By playing around, I found that if I remove setContentView method from the snippet above, the fragment shows up with the data. Any ideas as to why that was a problem? Thanks!
Edit: Here is my static newInstance method in the fragment
public static DetailActivityFragment newInstance(String id) {
DetailActivityFragment fragment = new DetailActivityFragment();
Bundle args = new Bundle();
args.putString(Intent.EXTRA_TEXT, id);
fragment.setArguments(args);
return fragment;
}
Here's my fragment from the layout activity_detail:
<fragment android:name="app.appone.DetailActivityFragment"
android:id="#+id/fragment_detail"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
You have to pass the data to your fragment.
Create a static method on your fragment for instance creation. It should look like this:
public static newInstance(Object param) {
DetailActivityFragment yourFragment = new DetailActivityFragment();
Bundle args = new Bundle();
args.put(key, value);
yourFragment.setArguments(args);
return yourFragment;
}
And in your onCreate method of the fragment you can get that data using the method "getArguments();
Your activity code is ok. But I would prefer using "replace" instead of "add" method.
Your latest edit shows you are using a static fragment in your layout xml, but creating it dynamically. A static fragment is created in your xml file:
<fragment android:name="app.appone.DetailActivityFragment"
android:id="#+id/fragment_detail"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Whereas a dynamic fragment is generated in your code with FragmentManager. It makes sense that calling setContentView() would cause a conflict, as the fragment you are creating with FragmentManager is being replaced by the fragment you are defining in your xml file. The one in your xml, unlike your dynamic fragment, has no arguments, which is why it's returning null.
As you use android.R.id.content, you can remove this static fragment from your xml completely. Replace it with an empty layout, such as FrameLayout, and set an id attribute. Then, when using FragmentManager, replace android.R.id.content for this id.
For example:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/frag_container" />
And in your Activity file:
DetailActivityFragment frag = (DetailActivityFragment) getSupportFragmentManager().findFragmentById(R.id.frag_container);
if (frag == null) {
frag = DetailActivityFragment.newInstance(id);
getSupportFragmentManager()
.beingTransaction()
.add(R.id.frag_container, frag)
.commit();
}
Thanks for your edit. I think you are using the wrong id for fragment replacement.
As in a previous comment you should first set the content view. Your layout file should have a placeholder view, e.g. Framelayout. Give your layout an id and reference this id in your replacement code.
Your "R.layout.activity_detail" should have a layout snippet like this:
<FrameLayout id="+#id/my_detail_frag"/>
And your activity code should look like this:
getSupportFragmentManager().beginTransaction().add(R.id.my_detail_frag,detailFrag).commit();
This answer will do the trick for you:
Best practice for instantiating a new Android Fragment
You should use setArguments() and getArguments() to pass the Bundle into the Fragment.
Good luck!

Android full-screen dialog callback issue

I am having trouble wrapping my head around something but let me first describe my setup:
I have an activity that references 3 fragments, each one of them get shown at the correct time. This is how the ChildrenSpecificationFragment looks:
If the user clicks the floating action button the following DialogFragment opens:
I found the following information in the new material design guidelines: https://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
Avoid dialogs that:
Open additional dialogs from within a dialog.
Contain scrolling content, particularly alerts. Instead, consider alternate containers or layouts that are optimized for reading or interacting with significant amounts of content.
Exceptions include:Full-screen dialogs may open additional dialogs, such as pickers, because their design accommodates additional layers of material without significantly increasing the app’s perceived z-depth or visual noise.
This is where my problems begin. The 'add child' dialog has scrollable content (in landscape mode) and when the user clicks 'Birth date' a date picker opens.
I am trying to find a way to implement a full screen dialog (as in the guidelines) that has a callback to the ChildrenSpecificationFragment, so that I can add the child to the RecyclerView .
I hope that my questing is clear and would greatly appreciate any input that would lead me to the solution. Thanks in Advance!
TL;DR - DialogFragment is insufficient for anything other than completely full-screen. Use an Activity instead.
It is possible to make a DialogFragment full-screen (with the ActionBar shown), but it comes with lots of irritations.
A DialogFragment is, as the name suggests, a Dialog and a Fragment rolled into one: it can be treated as both a Dialog, using show() and dismiss(), or as a Fragment, using it with a FragmentManager.
As the official documentation suggests, making a dialog completely full-screen (overlaying everything) is achieved by attaching the dialog to the root view android.R.id.content:
public void showDialog() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment newFragment = new CustomDialogFragment();
if (mIsLargeLayout) {
// The device is using a large layout, so show the fragment as a dialog
newFragment.show(fragmentManager, "dialog");
} else {
// The device is smaller, so show the fragment fullscreen
FragmentTransaction transaction = fragmentManager.beginTransaction();
// For a little polish, specify a transition animation
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity
transaction.add(android.R.id.content, newFragment)
.addToBackStack(null).commit();
}
}
To get the dialog to appear below the ActionBar, a FrameLayout is required which is used instead of the root layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Use ThemeOverlay to make the toolbar and tablayout text
white -->
<android.support.design.widget.AppBarLayout
android:id="#+id/abl_top"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:fitsSystemWindows="true"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header"
app:menu="#menu/nav_view"/>
</android.support.v4.widget.DrawerLayout>
Now comes the pain.
Depending on how the app's main navigation is setup, different hoops will need to be jumped through in order to get everything working perfectly.
The above example has a NavigationView. Since the home button android.R.id.home is handled in the main view, some logic is needed there to check if our dialog is shown so that the home button, which is now an X, will close the dialog. Returning false here allow the event to be handled in the dialog.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentById(R.id.content);
if (f instanceof MyDialogFragment) {
return false;
}
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
Also, the back button needs similar logic to determine whether the NavigationView needs closing or the ActionBar content resetting.
#Override
public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
FragmentManager fm = getSupportFragmentManager();
Fragment f = fm.findFragmentById(R.id.content);
if (f instanceof MyDialogFragment) {
final ActionBar ab = getSupportActionBar();
ab.setHomeAsUpIndicator(R.drawable.ic_menu);
ab.setTitle(R.string.app_name);
}
super.onBackPressed();
}
}
In the DialogFragment itself, the logic for closing the dialog (and abusing the ActionBar) needs to be implemented.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (mActionBar != null) {
mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
mActionBar.setTitle(R.string.app_name);
}
getActivity().getSupportFragmentManager().popBackStack();
case R.id.action_save:
if (mOnAcceptListener != null) {
mOnAcceptListener.onAccept();
}
if (mActionBar != null) {
mActionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
mActionBar.setTitle(R.string.app_name);
}
getActivity().getSupportFragmentManager().popBackStack();
return true;
}
return super.onOptionsItemSelected(item);
}
This alls feels really kludgy. Of course, if you're using a TabLayout, forget everything I've just said.
With a TabLayout you can just handle everything in the DialogFragment, but if you're using a ViewPager, it'll be impossible to get the dialog to cover the tabs but not the action bar. See Show DialogFragment over TabLayout.
That question (by me) has an answer that suggests the same as #Jdruwe, which is to forget the hopelessness of the DialogFragment and use an Activity instead.
A solution described on my blog using startActivityForResult(...): http://jeroendruwe.be/full-screen-dialogs-in-android/
I don't see code from your post. So I am guessing your code structure as a start.
First build your dialog with a listener and process setPositiveButton() and the onClick event.
Code suggestion:
public class ChildrenSpecificationFragment extends Fragment {
...
public void passData(Object obj) {
}
class SubChildFragment extends Fragment {
AlertDialog.Builder builder = new AlertDialog.Builder(thisContext);
...
// Add the buttons...
builder.setPositiveButton("Save", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
...
passData(Object obj); // pass data to the outer fragment class
Notes:
SubChildFragment, for example, is an inner class derived from Fragment. It can call the public method passData() in the outer class ChildrenSpecificationFragment for passing any data you need.
I am using an inner class because I think this is what you meant in your diagram by
Add child full-screen fragment
This coding technique is easier than starting a new Activity and Intent.
For showing fullscreen dialogs, there is a good Google webpage I think # Dialog - Fullscreen. Search text for "Showing a Dialog Fullscreen or as an Embedded Fragment".
add this line to oncreate in your custom dialog fragment.
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
On the other hand, you can use content resolvers to store your children datas.
It has observer pattern. So each CursorAdapter attached to that content it refreshes itself without calling notifySetDataChanged();.
I think you are using RecyclerView.Adapter. You can use this class.
Another advice for implementing adding child feature is using startActivityForResult(activity);.
You can send back datas by using getIntent().getExtras().put(key,value); You can search for custom start activity for result.
Good luck

Refresh List using Provider

INTRODUCTION
I am using a Database and a ContentProvider to fill a ListFragment. The problem is that ListFragment is not getting refreshed after I delete an item, I have to close and reopen the app to show the added item in the list.
I got this working well before, but after a modification I did in the code, this has started to happen and I don't know how to solve.
DESCRIPTION OF WHAT CAUSES THIS
From my main Activity, I call ListFragment via intent. As a fragment can't be called directly via intent, I call a FragmentActivity which functionality is only to start the ListFragment.
[Main] >> [FragmentActivity] >> [ListFragment]
As I have to handle single or dual pane, I created a layout which contais the Fragment, this is called list_layout.
FragmentActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_layout);
//...
if (mDualPane){
MyPlacesListFragment listFragment = new MyPlacesListFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.list_fragment, listFragment).commit();
}
else {
MyPlacesListFragment listFragment = new MyPlacesListFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(android.R.id.content, listFragment).commit();
}
}
list_layout.xml: (for the single pane view, for the dual pane will be similar but in horizontal orientation and adding a Framelayout container)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
class="com.final.reversegeocoding.MyPlacesListFragment"
android:id="#+id/list_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
The ListFragment uses a ResourceCursorAdapter so I use newVew and bindView methods. I'll only put the code refering to how I inflate the class ass I have checked that other code functionality is well done. Have to say that I inflate android.R.layout.simple_list_item_2 as this is the layout I need to fill the list.
ListFragment:
final class PlacesCursorAdapter extends ResourceCursorAdapter {
private final LayoutInflater mInflater;
public PlacesCursorAdapter(Context context, Cursor c) {
super(context, android.R.layout.simple_list_item_2, c, 0);
//...
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(android.R.layout.simple_list_item_2, parent, false);
//...
CONCLUSION
Before I did't set the setContentView(R.layout.list_layout); in the FragmentActivity and it did it right the refreshing when I deleted an item.
But due to I have to handle single or dual pane, I've been forced to do this and now, when I delete an item, the list refreshes, but because it is contained in the FragmentActivity's list_layout and this is not refreshed until I exit and enter again the activity, the list is not getting refreshed inside of it.
So my question is: How do I modify this code to get the layout refreshing without having to exit the ListFragment?
After modifying your data, call adapter.notifyDataSetChanged(),it will refresh your data
Well, as said in the post I knew that it had something to be with the layouts. I just had to modify the list_layout and the way I call it on FragmentActivity and it has started to work well again.
FragmentActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_layout);
MyPlacesListFragment listFragment = new MyPlacesListFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.list_fragment, listFragment).commit();
}
list_layout.xml: (for the single pane view, for the dual pane will be similar but in horizontal orientation and adding a Framelayout container)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Framelayout
android:id="#+id/list_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

Android: Single Fragment to display list and button at the same table

I have downloaded a sample app which has two fragments on it. The left fragment shows items and the right fragment shows contect based on which item is selected.
What I want to do is to also have a button on the left fragment which can be used to update the list that is shown. Hence why I want a list and a button to apppear on the same fragment at the same time.
Can this be done. My example code is: -
layout xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="#+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
Main activity code is: -
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first
// fragment
if (findViewById(R.id.fragment_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 an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from
// an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.fragment_container, firstFragment);
transaction.commit();
}
}
Is it possible to also have a button to appear after the R.id.fragment_container is done.
Thanks
Martin
There's a number of ways of doing this. The simplest way is to let the activity keep references to the fragments, and create a method in the list fragment that can be called by the other fragment through a method in the activity.
Another way of doing is via Broadcasts.
A third way of doing it is use RoboGuice and inject a class in both fragments that establishes communication between the fragments.

Categories

Resources