Home > Enterprise >  Fragments not working right (possible inflation error) (Android)
Fragments not working right (possible inflation error) (Android)

Time:10-04

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 Fragments, 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 call commitNow() to run the fragment transaction on your UI thread immediately.

Note that commitNow is incompatible with addToBackStack. Alternatively, you can execute all pending FragmentTransactions submitted by commit() calls that have not yet run by calling executePendingTransactions(). This approach is compatible with addToBackStack.

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 Fragments.


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 Fragments as self-contained as possible, and leave your Activity as simple as possible.)

  • Related