I have 3 types of fragments all being held in my fragment_container
in my classic_menu.xml
for my MainActivity.java
. I start with fragment A and by pressing a button go to fragment B via a method I have that uses FragmentTransaction.replace(R.id.fragment_container, B)
. The issue comes when I wish to go to fragment C from B using the same method. I'm getting a casting error using what you see below. Edit I get a null pointer by using findFragmentByTag()
instead of findFragmentById()
.
Here are the fragments in question:
Fragment A :
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.R;
public class MainMenuFragment extends Fragment{
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
View view = inflater.inflate(R.layout.main_menu_fragment, container, false);
return view;
}
}
Fragment B :
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.R;
public class ClassicMenuFragment extends Fragment{
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
View view = inflater.inflate(R.layout.classic_menu_fragment, container, false);
return view;
}
}
Fragment C :
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.MainActivity;
import com.example.R;
import com.example.widgets.TextViewPlus;
public class OnePlayerFragment extends Fragment{
private static TextViewPlus topScore;
private static TextViewPlus bottomScore;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle onSavedInstanceState){
View view = inflater.inflate(R.layout.one_player_fragment, container, false);
topScore = (TextViewPlus) view.findViewById(R.id.topPlayerScore1P);
bottomScore = (TextViewPlus) view.findViewById(R.id.bottomPlayerScore1P);
return view;
}
/**
* Changes the text of certain textViewPlus objects based on the given score
* @param view int value that determines which view to update
* @param score value to set the text to
*/
public void setScore(int view, int score){
if(view == MainActivity.TOP_PLAYER_1P) topScore.setText("" + score);
else if(view == MainActivity.BOTTOM_PLAYER) bottomScore.setText("" + score);
}
}
The buttons being used:
// in main_menu_fragment.xml
<com.example.widgets.ButtonPlus
android:id="@+id/classicB"
style="@style/button"
android:onClick="StartClassicMenu"/>
// in classic_menu_fragment.xml
<com.example.widgets.ButtonPlus
android:id="@+id/onePlayerB"
style="@style/button"
android:onClick="StartGame"/>
The MainActivity.java
:
// cut a lot of stuff for brevity
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.example.fragments.ClassicMenuFragment;
import com.example.fragments.MainMenuFragment;
import com.example.fragments.OnePlayerFragment;
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.classic_menu);
// cut more stuff
theView = (GameView)findViewById(R.id.theView); // get reference to the GameView
// begin the game in Animation mode and pass this MainActivity to the GameView so it can be passed along
theView.initiateGameThread(GameState.ANIMATION_MODE, this);
theThread = theView.getThread(); // get reference to the GameThread
theGame = theThread.getGameState(); // get reference to the GameState
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState != null) return;
MainMenuFragment mMenu = new MainMenuFragment();
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, mMenu).commit();
}
}
public void StartClassicMenu(View v){
changeToFragment(new ClassicMenuFragment(), "ClassicMenu");
inFragment = true;
}
public void StartGame(View v){
switch (v.getId()){
case R.id.onePlayerB:
theGame.setMode(GameState.ONE_PLAYER_MODE);
changeToFragment(new OnePlayerFragment(), "OnePlayer");
Log.d("MainActivity", "StartGame() for 1P mode called");
break;
// other cases here but cut out
theGame.reset();
}
// called from gamestate when views need to be updated
public void setViewScore(int view, int score){
if(theGame.getMode() == GameState.ONE_PLAYER_MODE){
Log.d("MainActivity", "setViewScore() for 1P mode called");
OnePlayerFragment f = (OnePlayerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if(f == null) Log.d("OnePlayerFragment", "null!!!");
f.setScore(view, score);
}
// other cases cut out
}
/**
* Handles creating and managing a uniform FragmentTransaction for the entire app
* @param newFragment the new Fragment that will fade in, replacing whichever fragment was in use
*/
public void changeToFragment(Fragment newFragment, String tag){
Log.d("MainActivity", "changeToFragment() Called with tag \"" + tag + "\"");
// Create the standard fade in/out transaction
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
// Replace the old fragment in the Relative Layout view with the new one
transaction.replace(R.id.fragment_container, newFragment, tag);
transaction.commit(); // Commit the transaction
}
The MainActivity
's respective xml layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/back_grey">
<com.example.GameView
android:id="@+id/theView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<RelativeLayout
android:id="@+id/rLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</RelativeLayout>
And now the best part:
07-18 16:30:01.999: D/OpenGLRenderer(12335): Enabling debug mode 0
07-18 16:30:02.259: D/GameView(12335): surfaceCreated() Called
07-18 16:30:02.649: I/Timeline(12335): Timeline: Activity_idle id: android.os.BinderProxy@1f1e5333 time:475861093
07-18 16:30:13.179: D/ViewRootImpl(12335): ViewPostImeInputStage ACTION_DOWN
07-18 16:30:13.389: D/MainActivity(12335): changeToFragment() Called with tag "ClassicMenu"
07-18 16:30:14.099: D/ViewRootImpl(12335): ViewPostImeInputStage ACTION_DOWN
07-18 16:30:14.169: D/MainActivity(12335): changeToFragment() Called with tag "OnePlayer"
07-18 16:30:14.169: D/MainActivity(12335): StartGame() for 1P mode called
07-18 16:30:14.179: D/MainActivity(12335): setViewScore() for 1P mode called
07-18 16:30:14.219: D/AndroidRuntime(12335): Shutting down VM
07-18 16:30:14.249: E/AndroidRuntime(12335): FATAL EXCEPTION: main
07-18 16:30:14.249: E/AndroidRuntime(12335): Process: com.brownapps.battlepong, PID: 12335
07-18 16:30:14.249: E/AndroidRuntime(12335): java.lang.IllegalStateException: Could not execute method of the activity
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.view.View$1.onClick(View.java:4222)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.view.View.performClick(View.java:5156)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.view.View$PerformClick.run(View.java:20755)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.os.Handler.handleCallback(Handler.java:739)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.os.Handler.dispatchMessage(Handler.java:95)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.os.Looper.loop(Looper.java:145)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.app.ActivityThread.main(ActivityThread.java:5835)
07-18 16:30:14.249: E/AndroidRuntime(12335): at java.lang.reflect.Method.invoke(Native Method)
07-18 16:30:14.249: E/AndroidRuntime(12335): at java.lang.reflect.Method.invoke(Method.java:372)
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
07-18 16:30:14.249: E/AndroidRuntime(12335): Caused by: java.lang.reflect.InvocationTargetException
07-18 16:30:14.249: E/AndroidRuntime(12335): at java.lang.reflect.Method.invoke(Native Method)
07-18 16:30:14.249: E/AndroidRuntime(12335): at java.lang.reflect.Method.invoke(Method.java:372)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.view.View$1.onClick(View.java:4217)
07-18 16:30:14.249: E/AndroidRuntime(12335): ... 10 more
07-18 16:30:14.249: E/AndroidRuntime(12335): Caused by: java.lang.ClassCastException: com.example.fragments.ClassicMenuFragment cannot be cast to com.example.fragments.OnePlayerFragment
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.example.MainActivity.setViewScore(MainActivity.java:325)
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.example.GameState$5.run(GameState.java:650)
07-18 16:30:14.249: E/AndroidRuntime(12335): at android.app.Activity.runOnUiThread(Activity.java:5517)
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.example.GameState.reset(GameState.java:647)
07-18 16:30:14.249: E/AndroidRuntime(12335): at com.example.MainActivity.StartGame(MainActivity.java:146)
07-18 16:30:14.249: E/AndroidRuntime(12335): ... 13 more
The only other info you may need is that in theGame
's reset()
method it calls setViewScore()
on the MainActivity
object passed to it through theView.initiateGameThread(GameState.ANIMATION_MODE, this);
by using runOnUiThread()
.
So, my question is then why does the first time I call changeToFragment()
change the MainMenuFragment
to a ClassicMenuFragment
, but screws up the second time when it's supposed to change the ClassicMenuFragment
to a OnePlayerFragment
?
Thank you for your time and consideration with this problem of mine.
The ClassCastException
is correct - you can't cast a ClassicMenuFragment
to a OnePlayerFragment
. In terms of inheritance, you cannot cast one sibling to another (both of these classes are siblings, with a common parent Fragment
). An analogy is Orange
and Apple
are both children of a class Fruit
, but you cannot cast an Orange
to an Apple
(that doesn't make sense!)
Instead, remove your cast to OnePlayerFragment
and make use of the instanceof
keyword, only casting once you are sure which child instance your fragment is:
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f == null) Log.d("Fragment", "null!!!");
if (f instanceof OnePlayerFragment) {
((OnePlayerFragment) f).setScore(view, score);
}