0

I'm currently making a soundboard app on android studio which has a ton of fragments. The first page on my tab layout is just a normal button view, but my second tabView has another fragmentView within it. The goal is for the first page to just show some normal sounds, and the second tab to have multiple operators to choose specific soundboards for.

Anyways, the app seems to work fine. I can switch from the main tab to the operator tab, and even select operator soundboard pages and be moved to their fragments. However as soon as I try to switch fragments (through my tabview) my app crashes and I get the error:

"Fragment operatorTab{4623fc9} (312d4e58-458c-4f47-8fa3-794fe15f0536)} not attached to a context."**
**at com.jkcarraher.rainbowsixsoundboard.operatorTab.resetAllButtons(operatorTab.java:113)
        at com.jkcarraher.rainbowsixsoundboard.operatorTab.access$000(operatorTab.java:31)
        at com.jkcarraher.rainbowsixsoundboard.operatorTab$2.onScrollChanged(operatorTab.java:107)

I attached my code below, please let me know if you have any ideas!

MainActivity.java

import android.graphics.Color;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.widget.Adapter;
import android.widget.TextView;

import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.google.android.material.tabs.TabLayout;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private SectionsPageAdapter mSectionsPageAdapter;
    private ViewPager mViewPager;
    private TabLayout tabLayout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeAds();
        makeSixYellow();

        //Create ViewPager (that houses all fragments)
        mSectionsPageAdapter = new SectionsPageAdapter(getSupportFragmentManager());
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        setUpViewPager(mViewPager);

        //Add & customize tabs
        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setTabTextColors(Color.WHITE, Color.WHITE);
        tabLayout.setupWithViewPager(mViewPager);
        tabLayout.getTabAt(0).setText("Utility");
        tabLayout.getTabAt(1).setText("Voice Lines");
        checkPageChange();
    }

    private void setUpViewPager(ViewPager viewPager) {
        SectionsPageAdapter adapter = new SectionsPageAdapter(getSupportFragmentManager());
        adapter.addFragment(new utilityTab(), "Utility");
        adapter.addFragment(new voiceLinesView(), "Voice Lines");

        viewPager.setAdapter(adapter);
    }

    private void makeSixYellow(){
        TextView textView = findViewById(R.id.titleText);
        String text = "R6 Soundboard";
        SpannableString ss = new SpannableString(text);
        ForegroundColorSpan fcsYellow = new ForegroundColorSpan(Color.rgb(255,236,141));
        ss.setSpan(fcsYellow, 1,2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        textView.setText(ss);
    }

    private void initializeAds(){
        MobileAds.initialize(this, new OnInitializationCompleteListener() {
            @Override
            public void onInitializationComplete(InitializationStatus initializationStatus) {
            }
        });
    }

    private void checkPageChange(){
        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                utilityTab.resetAllButtons();
            }

            @Override
            public void onPageSelected(int position) {
                utilityTab.resetAllButtons();
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                utilityTab.resetAllButtons();
            }
        });
        }

}

activity_main.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorHeader"
        android:theme="@style/AppTheme.AppBarOverlay">

        <TextView
            android:id="@+id/titleText"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="@font/avenir"
            android:gravity="center_horizontal"
            android:text="R6 Soundboard"
            android:textColor="#FFF"
            android:textSize="30sp" />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabTextColor="#fff"
            android:background="@color/colorHeader" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorBody"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

voiceLinesView.java (contains fragments so I can select an operator and it will take me to their soundboard fragment)

package com.jkcarraher.rainbowsixsoundboard;

import android.os.Bundle;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.ViewPager;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.material.tabs.TabLayout;

public class voiceLinesView extends Fragment {

    private SectionsPageAdapter voiceLinesSectionsPageAdapter;
    private ViewPager voiceLinesViewPager;

    public voiceLinesView() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_voice_lines_view, container, false);

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.voiceLinesFrame, new operatorTab());
        fragmentTransaction.commit();


        return view;
    }

}

fragment_voice_lines_view.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".voiceLinesView">

    <FrameLayout
        android:id="@+id/voiceLinesFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

operatorTab.java

package com.jkcarraher.rainbowsixsoundboard;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import android.widget.ScrollView;

import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;

import soup.neumorphism.NeumorphCardView;
import soup.neumorphism.ShapeType;

import static android.view.MotionEvent.ACTION_MOVE;

public class operatorTab extends Fragment {
    private ScrollView scrollView;
    public static RelativeLayout KapkanButton;
    public static RelativeLayout GlazButton;
    public static RelativeLayout FuzeButton;
    public static RelativeLayout TachankaButton;

    voiceLinesView voiceLinesView = new voiceLinesView();


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_operator_tab, container, false);

        //Initialize ScrollView
        scrollView = view.findViewById(R.id.operatorScrollView);

        //Initialize buttons 1-4
        KapkanButton = view.findViewById(R.id.kapkanButton);
        GlazButton = view.findViewById(R.id.glazButton);
        FuzeButton = view.findViewById(R.id.fuzeButton);
        TachankaButton = view.findViewById(R.id.tachankaButton);

        //Make buttons 1-6 pressable
        initPressableButton(KapkanButton);
        initPressableButton(GlazButton);
        initPressableButton(FuzeButton);
        initPressableButton(TachankaButton);

        scrollResetListener();
        return view;
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initPressableButton(final RelativeLayout relativeLayout) {
        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
        final Rect r = new Rect();

        relativeLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent event) {
                // get the View's Rect relative to its parent
                view.getHitRect(r);
                // offset the touch coordinates with the values from r
                // to obtain meaningful coordinates
                final float x = event.getX() + r.left;
                final float y = event.getY() + r.top;

                if(event.getAction() == MotionEvent.ACTION_DOWN) {
                    relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorButtonPressed));
                    return true;
                } else if(event.getAction() == MotionEvent.ACTION_UP) {
                    if (r.contains((int) x, (int) y)) {
                        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
                        //On Click Up
                        FragmentTransaction fr = getFragmentManager().beginTransaction();
                        fr.replace(R.id.voiceLinesFrame, new kapkanVoiceLines());
                        fr.commit();
                    }

                }else if(event.getAction() == ACTION_MOVE){
                    if (!r.contains((int) x, (int) y)) {
                        relativeLayout.setBackgroundColor(getResources().getColor(R.color.colorBody));
                    }
                    return true;
                }
                return false;
            }
        });
    }

    private void scrollResetListener(){
        scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                resetAllButtons();
            }
        });
    }

    public void resetAllButtons(){
        KapkanButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        GlazButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        FuzeButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
        TachankaButton.setBackgroundColor(getResources().getColor(R.color.colorBody));
    }
}

fragment_operator_tab.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".operatorTab">

    <ScrollView
        android:id="@+id/operatorScrollView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorBody">
            <RelativeLayout
                android:id="@+id/kapkanButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/kapkanIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_kapkan"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/kapkanIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Kapkan"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/glazButton"
                android:layout_below="@+id/kapkanButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/glazIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_glaz"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/glazIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Glaz"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/fuzeButton"
                android:layout_below="@+id/glazButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/fuzeIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_fuze"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/fuzeIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Fuze"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>
            <RelativeLayout
                android:id="@+id/tachankaButton"
                android:layout_below="@+id/fuzeButton"
                android:layout_width="match_parent"
                android:layout_height="60sp"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/tachankaIcon"
                    android:layout_width="50sp"
                    android:layout_height="50sp"
                    android:layout_marginTop="5sp"
                    android:layout_marginLeft="10sp"
                    android:src="@drawable/ic_tachanka"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/tachankaIcon"
                    android:layout_marginLeft="10sp"
                    android:layout_marginTop="15sp"
                    android:layout_gravity="center"
                    android:fontFamily="@font/avenir"
                    android:text="Tachanka"
                    android:textColor="#ffffff"
                    android:textSize="25dp" />

                <ImageView
                    android:layout_width="40sp"
                    android:layout_height="40sp"
                    android:layout_alignParentEnd="true"
                    android:layout_marginTop="10sp"
                    android:layout_marginEnd="10dp"
                    android:src="@drawable/ic_arrow_right" />
            </RelativeLayout>

        </RelativeLayout>
    </ScrollView>

</FrameLayout>
Zain
  • 37,492
  • 7
  • 60
  • 84

2 Answers2

1

Try to remove the ViewTreeObserver.OnScrollChangedListener when the fragment is destroyed this will avoid any listener that can be attached to a destroyed fragment context not to be triggered whenever you leave and come back to this fragment.

First: make a global field for the listener

public class operatorTab extends Fragment {

    ...

    ViewTreeObserver.OnScrollChangedListener  mScrollListener = new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            resetAllButtons();
        }
    };

Then set the listener wit the created field

private void scrollResetListener(){
    scrollView.getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
}

Then remove the listener.. I believe it's typically should be in onDestroyView() >> but also try it in onStop() or onPause if it didn't work

This also will solve memory leak of this listener whenever its callback is triggered after the Fragment's view is destroyed

@Override
public void onDestroyView() {
    super.onDestroyView();
    scrollView.getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
}

UPDATE

onStop()/onPause() worked for me

The reason that it didn't work with onDestroyView() because the OperatorTab fragment is a part of one of the ViewPager pages; and by default ViewPager loads a number of close pages in the background to be ready for the next page scroll; loading this pages include some of the fragment lifecycle callbacks like onCreateView, onStart, but not onResume.

When you leave the OperatorTab by scrolling the ViewPager to the next tab/page; onDestroyView() won't be created if the ViewPager decides that OperatorTab is a part of the cached/close pages that the user might return back to it later; and therefore the listener still there and after a few swipes of the pager, the OperatorTab fragment can be detached from the context leaving the listener there with memory leaks...

So, the best way in ViewPager fragment (or any View that loads its fragments in advance) is to stop any listeners once you leave the page, i.e. in onPause or onStop callbacks and not waiting until they are destroyed.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • 1
    THANK YOU SO MUCH! YOU ARE ABSOLUTELY BASED. onStop()/onPause() worked for me – John Carraher Jan 09 '21 at 02:07
  • Hey @JohnCarraher just figured out why that didn't work in `onViewCreated`.. please have a look at UPDATE section of the answer .. happy coding :) – Zain Jan 09 '21 at 05:00
0

A possible reason for this error is that you are calling getResources() NOT in an Activity without using a context. So, you need to use context.getResources() instead where context may be one of these here . In you instance, I think getContext() will work.

So, I think you should search for all the times you are calling getResources and see if you are using a context or not.

The explanation for this error in a simple way is that you are trying to access the context required in getResources() before the fragment is instantiated.

Thanasis M
  • 1,144
  • 7
  • 22
  • I added this, however I get a different error: "java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference" on the same lines as above – John Carraher Jan 09 '21 at 00:44
  • Please try also the getApplicationContext() instead of getContext(), maybe this will help. And getActivity(). – Thanasis M Jan 09 '21 at 00:53
  • unfortunately this didn't work out either. I still get the error: "java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context androidx.fragment.app.FragmentActivity.getApplicationContext()' on a null object reference" – John Carraher Jan 09 '21 at 01:55