In activity_main
, I have a FrameLayout
. I want it so when I press one button, a CalcFragment
pops up in there, and when I press another button, a GraphFragment
pops up in there. I also want CalcFragment
to be the thing in there when the app first starts up. I didn't make GraphFragment
yet, because I'm still trying to get CalcFragment
to work right.
All my code worked when I used a fragment instead of a FrameLayout
, and manually told the fragment to show CalcFragment
. When I use a FrameLayout
, though, the entire app crashes.
Here's the code for the CalcFragment.java
:
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class CalcFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_calc, container, false);
}
}
Here's the segment of code in MainActivity.java
's onCreate
function which is SUPPOSED to put the CalcFragment
in the FrameLayout
:
Fragment calcFragment = new CalcFragment();
Fragment graphFragment = new GraphFragment();
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.replace(R.id.frag_container, calcFragment);
fragTrans.addToBackStack(null);
fragTrans.commit();
After that code is a bunch of code that sets the functionality of all the buttons in the CalcFragment
. When I tried to launch the application, it gave me an error that basically says the UI elements I referenced (the ones in the fragment) don't exist. Even though, if I delete the code, the UI elements clearly DO exist. I'm guessing this is an inflation error, and for some reason the code is just having trouble inflating the UI elements from the fragment with the UI elements from the main activity. I think, I don't really know. I also tried moving all this code to the CalcFragment.java
onCreate
function, but that in turn spawned a whole other list of issues I can't even begin to understand. I only barely know how fragments work, I'm just doing what 2 YouTube tutorials told me was a good idea.
Please help, I have no idea what's going on. Thank you!
EDIT: here's a google drive link if you want to see anything else in the project.
https://drive.google.com/drive/folders/1E3inSfdbKkTFjX44pWBBRt545rs34C4q?usp=sharing
Or, if you don't feel like using google drive, here's every single line of code on the entire project:
MainActivity.java:
package com.example.chrisscalculator2;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.text.*;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import android.view.*;
import java.util.HashMap;
import complexnumbers.*;
//import android.*;
public class MainActivity extends AppCompatActivity {
public static int historyDepth = 100; //how many entries in equation history
public static int clearCount = 0; //how many times we've hit the clear button in a row
public static boolean dotPress = false; //how many times we've hit the . button in a row (false=even, true=odd)
public static boolean ePress = false; //how many times we've hit the e button in a row (false=even, true=odd)
@Override
protected void onCreate(Bundle savedInstanceState) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
getWindow().setNavigationBarColor(Color.parseColor("#000000"));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//LayoutInflater inflater = getLayoutInflater();
//View graphLayout = inflater.inflate(R.layout.graph_layout, null);
Fragment calcFragment = new CalcFragment();
//Fragment graphFragment = new GraphFragment();
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.replace(R.id.frag_container, calcFragment);
fragTrans.addToBackStack(null);
fragTrans.commit();
final boolean[] secKeys = {false};
EditText calc_inp = (EditText) findViewById(R.id.calc_input);
calc_inp.setShowSoftInputOnFocus(false);
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/main_layout"
android:layout_width="1080px"
android:layout_height="2340px"
android:background="#000000"
tools:context=".MainActivity">
<Button
android:id="@ id/graph_button_1"
android:layout_width="240px"
android:layout_height="90px"
android:backgroundTint="#0000FF"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:text="Graph"
android:textAllCaps="false"
android:textSize="42px"
app:layout_constraintStart_toEndOf="@ id/equations_button_1"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@ id/calc_button_1"
android:layout_width="240px"
android:layout_height="90px"
android:backgroundTint="#0000FF"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:text="Calc"
android:textAllCaps="false"
android:textSize="42px"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@ id/equations_button_1"
android:layout_width="240px"
android:layout_height="90px"
android:backgroundTint="#0000FF"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"
android:text="Equations"
android:textAllCaps="false"
android:textSize="42px"
app:layout_constraintStart_toEndOf="@ id/calc_button_1"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@ id/frag_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@ id/equations_button_1"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here's fragment_calc.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/fragment_calc_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
tools:context=".CalcFragment">
<EditText
android:id="@ id/calc_input"
android:layout_width="0dp"
android:layout_height="135px"
android:layout_marginBottom="30px"
android:background="@drawable/shape"
android:ems="10"
android:inputType="none"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textColor="#00FFFF"
android:textSize="54px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="SpeakableTextPresentCheck" />
</android.support.constraint.ConstraintLayout>
Here's CalcFragment.java:
package com.example.chrisscalculator2;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class CalcFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_calc, container, false);
}
}
P.S. I know I'm using pixels instead of dp. Trust me, I have a very very very very very very good reason for that.
CodePudding user response:
Since the info I asked for is in the edit history, I'll show you what the problem is:
Fragment calcFragment = new CalcFragment();
FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction();
fragTrans.replace(R.id.frag_container, calcFragment);
fragTrans.addToBackStack(null);
fragTrans.commit();
...
EditText calc_inp = (EditText) findViewById(R.id.calc_input);
calc_inp.setShowSoftInputOnFocus(false);
There's a big guide on Fragment
s, and here's an important bit about transactions:
Calling
commit()
doesn't perform the transaction immediately. Rather, the transaction is scheduled to run on the main UI thread as soon as it is able to do so. If necessary, however, you can callcommitNow()
to run the fragment transaction on your UI thread immediately.Note that
commitNow
is incompatible withaddToBackStack
. Alternatively, you can execute all pending FragmentTransactions submitted bycommit()
calls that have not yet run by callingexecutePendingTransactions()
. This approach is compatible withaddToBackStack
.
So when you call commit()
, the Fragment
isn't actually added yet - it happens later. If you immediately call supportFragmentManager.getFragments()
you'll get an empty list, because the fragment hasn't been added yet.
If you immediately call supportFragmentManager.executePendingTransactions()
then the Fragment
will be added, and it will show up in the list of Fragment
s.
But that's not your only problem - just because the fragment has been added, its view (i.e. its layout hierarchy) hasn't been inflated yet. Fragments have their own lifecycle - onCreate
, onCreateView
etc that they have to run through, and immediately after constructing one and adding it to your FragmentManager
, it's not in the CREATED
state yet.
That means you can't do findViewById
(which you're running on the Activity
's layout) because the Fragment
's view (its layout hierarchy) hasn't been inflated yet, so no view with that ID exists in the overall layout yet. It will happen later. I'm being vague about that, because you can't be sure exactly when that will be, without hooking into the Fragment to check when it's ready, or other complicated things like that.
And because Activities
and Fragments
have their own separate lifecycles, and they can be recreated independently in any order (e.g. when you rotate the screen and everything is destroyed), you can't even be sure which will be ready when. You could observe Lifecycle
objects but... you shouldn't need to be doing that at all, it's pretty advanced - especially when you just want to display a Fragment, the most basic stuff.
And if that sounds complicated, yeah - that's partly why the Navigation component was created, so devs don't have to worry about a lot of this stuff. But in this case, the simplest approach is to keep things separate - the Activity
shouldn't need to know when the Fragment
's view is created. You should be able to just add the Fragment and have it work. So this:
calc_inp.setShowSoftInputOnFocus(false);
could go in the Fragment
's setup, onCreate
or whatever. It's part of the Fragment's own initialisation, right? Let it take care of its own business, wire up its own views and listeners and set things up however they're supposed to work. That way, you can just drop a Fragment
into an Activity
and it Just Works - the Activity
doesn't need to go poking around in there to finish setting things up.
(There are cases where you might need to wire some stuff up so the Activity
and Fragment
can communicate - but is this one of them? Does the Activity
need to be involved here? That's the mindset you need to be in - make your Fragment
s as self-contained as possible, and leave your Activity
as simple as possible.)