Null Object that is a child of a Fragment - android

I have a Fragment that is part of a ViewPager. In this Fragment I have a ViewGroup child. Now, why in my MainActivity's onCreate() after having instantiated my ViewPager and adapter, my Container is getting null?
Here is my onCreate():
private MyAdapter mAdapter;
private ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mContainerView = (ViewGroup) findViewById(R.id.container);
//Here mContainerView is already null
...
}
Here is the Fragment that is part of a ViewPager that contains mContainerView
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is my ViewGroup -->
<LinearLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:dividerHorizontal"
android:animateLayoutChanges="true"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</ScrollView>
<TextView android:id="#android:id/empty"
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="32dp"
android:text="#string/message_empty_layout_changes"
android:textColor="?android:textColorSecondary" />
</FrameLayout>

If I'm reading your question correctly, you're trying to access the Fragment's View (and children) using the Activity's findViewById() method. That shouldn't work since the Fragment inflates its own layout and Fragments are not traditional Views.
If you know the Fragment is instantiated and you can retrieve it, you can get an instance of the ViewGroup using
yourFragment#getView().findViewById()
If not, you can make an interface that your Activity implements with a method that accepts a ViewGroup as the argument. Then in the Fragment's onCreateView(), have the Fragment pass the ViewGroup off to the interface. You can cast directly to your Activity, but an interface is cleaner.
Eg
public class Fragment {
public interface ViewGroupCreateListener{
public void onViewGroupCreated (ViewGroup v);
}
private ViewGroupCreateListener listener;
public void onAttach (Activity a){
super.onAttach (a);
listener = (ViewGroupCreateListener) a;
}
public View onCreateView (/*all its arguments here*/){
View v = inflater.inflate (R.layout.your_layout);
ViewGroup group = v.findViewById (R.id.container);
listener.onViewGroupCreated(group);
return v;
}
}
Your Activity would look something like:
public class MainActivity extends Activity implements ViewGroupCreateListener, OtherInterface1, OtherInterface2{
private ViewGroup mViewGroup;
public void onViewGroupCreated (ViewGroup v){
mViewGroup = v;
}
}
This is nice since if the pager re-instantiates the Fragment, the Activity still gets a valid instance of the ViewGroup.
Or, if depending on what you're actually trying to achieve with this ViewGroup, you may be able to do this processing inside the Fragment itself.

Related

Floating action button causing memory leak

I have an app am working on, the app is using leak canary to detect possible memory leaks. The app seems to be fine except for my extended floating action button, according to leak canary the button is leaking, I do not have an idea on how to rectify this.
Below is my XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.store.StoreFragment">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/small_margin"
app:cardElevation="#dimen/normal_elevation"
app:shapeAppearanceOverlay="#style/ShapeAppearanceOverlayLargeCutLeftTopCorner">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/swipeToRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.core.widget.NestedScrollView
android:id="#+id/nestScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ViewStub
android:id="#+id/layoutCategories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutCategories"
android:layout="#layout/store_layout_categories" />
<ViewStub
android:id="#+id/layoutDeals"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutDeals"
android:layout="#layout/store_layout_deals" />
<ViewStub
android:id="#+id/layoutCollections"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutCollections"
android:layout="#layout/store_layout_collections" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/btnGoToCart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:text="#string/cart"
android:textColor="#android:color/white"
android:textStyle="bold"
app:backgroundTint="#color/colorAccent"
app:elevation="#dimen/large_elevation"
app:icon="#drawable/ic_shopping_cart_24px"
app:iconTint="#android:color/white"
app:layout_anchor="#id/nestScrollView"
app:layout_anchorGravity="bottom|end"
app:shapeAppearanceOverlay="#style/ShapeAppearanceOverlayLargeCutLeftTopCorner" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And my Java code
public class StoreFragment extends Fragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_store, container, false);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
btnGoToCart.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
....
case R.id.btnGoToCart:
Navigation.findNavController(v).navigate(R.id.cartFragment);
break;
....
}
}
}
Below is a snippet from leak canary. At first the leak was pointing to the ID of the coordinator layout, I removed it, now it is pointing to the Extended floating action button.
Instead of having the Fragment implement OnClickListener why not just create a new OnClickListner inner class? From what I see you ar passing the fragment as the OnClickListener and the view later holds a reference to your fragment and that's probably the cause of the leak. Just do (new OnClickListener () {}); instead of implementing OnClickListener.
EDIT:
I've just noticed that you are using ButterKnife in a Fragment.
If your using ButterKnife in a Fragment you should use:
private Unbinder unbinder;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
#Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
BINDING RESET Fragments have a different view lifecycle than
activities. When binding a fragment in onCreateView, set the views to
null in onDestroyView. Butter Knife returns an Unbinder instance when
you call bind to do this for you. Call its unbind method in the
appropriate lifecycle callback.

id attribute in fragments

I am working on a app and it runs just fine but there is an problem that it crashes when I dont write id in my fragment code but runs fine after using id attribute and the problem is that i have not used it's id anywhere in my app
Here is the xml file
<?xml version="1.0" encoding="utf-8"?>
<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:orientation="horizontal"
>
<fragment
class="com.hfad.workout.WorkoutList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="#+id/listfrag"
/>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="second text view"
android:layout_marginTop="20dp"
android:id="#+id/fragcont"
android:layout_weight="3"
/>
</LinearLayout>
workoutlist.jav
public class WorkoutList extends ListFragment {
MainActivity ma;
static interface WorkoutListListener{
void clickme(long at);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayAdapter<Workout> arrayAdapter = new ArrayAdapter<Workout>(
inflater.getContext(), android.R.layout.simple_list_item_1,
Workout.workout);
setListAdapter(arrayAdapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onAttach(Context ac){
super.onAttach(ac);
this.ma =(MainActivity) getActivity();
}
public void onListItemClick(ListView v, View vi, int position, long id){
ma.clickme(id);
}
}
Before an activity can talk to its fragment, the activity first needs
to get a reference to it. To get a reference to the fragment, you first
get a reference to the activity’s fragment manager using the
activity’s getFragmentManager() method. You then use its
findFragmentById() method to get a reference to the fragment:
findFragmentById() is a
bit like findViewById()
except you use it to get a
reference to a fragment
getFragmentManager().findFragmentById(R.id.fragment_id)
It's because if you do not specify fragment id, then FragmentManager cannot restore it after recreating.
You have to specify fragment id or tag.

Nested Fragments. How to know when all of them are attached to the activity?

My application uses multiple nested fragments. Some of these fragments are placeholders. When I try to replace placeholders with initial set of fragments I get
java.lang.IllegalStateException: Activity has been destroyed
I am sure that activity is fine and the issue is caused by fragments not being attached to the activity. So how do know when all nested fragments are attached to the activity?
Additional information about my setup:
I have an Activity that has fragment declaration in it's layout file:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/rightPane"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.company.PagerFragment"/>
</LinearLayout>
The PagerFragment is the host for view pager.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
I put two Fragments into the pager. They serve as placeholders.
I want to replace placeholders with initial fragments but when I initiate the replacement in activity's onCreate method I get the aforementioned exception.
Everything works fine if I use new Handler().postDelayed(..) in activity's onCreate method. I would rather to not use the delay approach for obvious reasons.
I tried to replace placeholders in other activity's methods like onStart(), onResume(), onResumeFragments(), onPostResume() but that didn't help.
EDIT:
public class PagerFragment extends Fragment
{
..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_right_pane, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null)
{
m_primary = new PlaceholderFragment();
m_secondary = new PlaceholderFragment();
}
else
{
FragmentManager fm = getChildFragmentManager();
m_primary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_PRIMARY);
m_secondary = (PlaceholderFragment)fm.getFragment(savedInstanceState, FRAGMENT_TAG_SECONDARY);
}
Fragment[] fragments = new Fragment[] { m_primary, m_secondary };
PagerAdapter pagerAdapter = new FragmentPagerAdapter(getChildFragmentManager(), fragments);
m_viewPager = (ViewPager)view.findViewById(R.id.pager);
m_viewPager.setAdapter(pagerAdapter);
m_viewPager.setOnPageChangeListener(this);
m_viewPager.setCurrentItem(0, true);
}
..
}

NullPointerException when calling findViewById() to get a Fragment's View

I'm trying to setup a FragmentStatePagerAdapter to create a scrollable tab interface on one of my fragments similar to this example. In my activity's onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.base);
setVersionString();
setupActionBar();
setupNavigationDrawer();
setupFragments();
}
I set the content view to the following layout (R.layout.base):
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BaseActivity" >
<!-- The main content view -->
<FrameLayout
android:id="#+id/main_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The navigation drawer -->
<ListView
android:id="#+id/nav_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#FFF" />
</android.support.v4.widget.DrawerLayout>
I then call the following function:
private void setupFragments() {
// Check that the activity is using the correct layout
if (findViewById(R.id.main_frame) != null) {
// Initialize the first fragment
MainFragment firstFrag = new MainFragment();
// Pass any extras from an intent to the Fragment
firstFrag.setArguments(getIntent().getExtras());
// Add the first Fragment to the screen
getSupportFragmentManager().beginTransaction()
.add(R.id.main_frame, firstFrag).commit();
tabsAdapter = firstFrag.getAdapter();
Log.d("Base.LOG", "tabsAdapter = " + tabsAdapter);
tabsAdapter.addTab(DummyFragment.class, null);
}
}
In an attempt to set a FragmentStatePagerAdapter as the adapter for the view in the fragment's layout (R.layout.main):
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment" >
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
And my fragment's code looks lie this:
private View mView;
private TabbedPagerAdapter mTabPageAdapter;
private ViewPager mTabPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("Main.LOG", "onCreate called");
mView = inflater.inflate(R.layout.main, container, false);
Log.d("Main.LOG", "Layout inflated");
setupTabs();
Log.d("Main.LOG", "Tabs setup");
return mView;
}
#Override
public void onResume() {
super.onResume();
Log.d("Main.LOG", "onResume called");
}
/** Set up the ViewPager and Adapter to handle the primary tabs */
private void setupTabs() {
// Initialize the adapter
mTabPageAdapter = new TabbedPagerAdapter(getActivity(),
getActivity().getSupportFragmentManager());
// Initialize the view pager
mTabPager = (ViewPager) mView.findViewById(R.id.main_page);
Log.d("Main.LOG", "mTabPager = " + mTabPager);
Log.d("Main.LOG", "mTabPageAdapter = " + mTabPageAdapter);
mTabPager.setAdapter(mTabPageAdapter);
}
/* Accessor for TabbedPagerAdapter */
public TabbedPagerAdapter getAdapter() {
return mTabPageAdapter;
}
The app force closes when I try to add a tab with a NullPointerException. The debug messages in the log show that:
D/Base.LOG(27173): tabsAdapter = null
is displayed before any of the fragment's debug messages. How can I ensure that the layout is inflated before trying to access any of it's Views or subclasses?
As per my comment
public class MainFragment extends Fragment
{
private MainInterface mInterface = null;
//make sure to set the above somehow
public interface MainInterface{
public void onFragmentReady(.....);
}
... //Do regular stuff here
public void onResume()
{
//Do what you need to do in onResume
if(mInterface != null)
{
mInteface.onFragmentReady();
}
}
}
This should allow you to know when the fragment is ready and you can do your getTabAdapter calls then.
Are you calling setContentView(...) inside the onCreate(...) method of your Activity before calling setupFragments(...)?
If yes: Does the layout you are assigning to the Activity contain the ViewPager?
My recommendation: Since you are obviously using a ViewPager inside a Fragment, do the initialization of the ViewPager inside the Fragments onCreateView(...) method.
Example:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ASSUMING your layout.main contains the ViewPager
View v = inflater.inflate(R.layout.main, container, false);
ViewPager pager = (ViewPager) v.findViewById(R.id.pager);
// and so on ...
return v;
}

Custom Adapter within a Nested Fragment

I have a ViewPager which holds multiple base fragments, each base fragment have four more nested fragments and each nested fragment is a combination of 5 imageViews and textView.
(This is what I intended to do)
I have created a sample application but I am not able to implement that properly. I cannot see any view in the nested fragment or the base fragment.
Here's the code for the sample application
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<fragment android:name="com.example.nestedfragments.BaseFragment"
android:id="#+id/left_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"/>
</LinearLayout>
MainActivity.java
package com.example.nestedfragments;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
base_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:id="#+id/button1"
android:text="Launch Nested Fragment"
android:gravity="center"
android:layout_alignParentBottom="true"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingBottom="10dp"/>
</RelativeLayout>
BaseFragment.java
public class BaseFragment extends Fragment {
Button doNestingButton;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// set the view
View root = inflater.inflate(R.layout.base_fragment, container, false);
doNestingButton = (Button) root.findViewById(R.id.button1);
doNestingButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Fragment videoFragment = new NestedFragment();
// we get the 'childFragmentManager' for our transaction
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
// make the back button return to the main screen
// and supply the tag 'left' to the backstack
transaction.addToBackStack("left");
// add our new nested fragment
transaction.add(getId(), videoFragment, "left");
// commit the transaction
transaction.commit();
}
});
return root;
}
}
nested_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="154dp"
android:layout_height="154dp"
android:id="#+id/imageView" android:layout_gravity="left|center_vertical"
android:src="#drawable/ic_splash"/>
</LinearLayout>
NestedFragment.java
public class NestedFragment extends Fragment {
public NestedFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.nested_fragment, container, false);
ImageView doNestingButton = (ImageView) root.findViewById(R.id.imageView);
return root;
}
Once again this is a sample application, please guide me.
From your above code, you have not used the fragment container layout such as FrameLayout in base_fragment.xml .So add the frame layout for nested fragment inbase_fragment.xml

Categories

Resources