Integration Android Fragment in react native's fabric architecture leads to blank view

359 views Asked by At

I am trying to integrate Android Fragment with react native's new architecture fabric. I am following the guide from over here unfortunately they have not specified any docs for integrating fragment so I was taking help from the old architecture over here

I have created following kotlin files, the rest of the code of adding typescript interface remains the same

class CenteredText(context: Context): LinearLayout(context){

    private var textView: TextView
    private var secondtextView: TextView

    init {
        val layoutParams: ViewGroup.LayoutParams =
            LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        setLayoutParams(layoutParams)
        orientation = VERTICAL

        textView = TextView(context)
        addView(textView)

        secondtextView = TextView(context)
        addView(secondtextView)
        secondtextView.text = "Hello I am second"
        secondtextView.textSize = 25F
        secondtextView.setTextColor(Color.RED)
    }

    fun configureText(text: String){
        textView.text = text
    }

}


class CenteredTextManager(private val context: ReactApplicationContext) :
    ViewGroupManager<FrameLayout>() {

    private val create = "create"
    private var propWidth: Int? = null
    private var propHeight: Int? = null

    override fun getName(): String {
        return NAME
    }

    override fun createViewInstance(reactContext: ThemedReactContext) = FrameLayout(reactContext)

    companion object {
        const val NAME = "RTNCenteredText"
        private const val COMMAND_CREATE = 1
    }

    override fun getCommandsMap() = mapOf(create to COMMAND_CREATE)

    override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
        super.receiveCommand(root, commandId, args)
        val reactNativeViewId = requireNotNull(args).getInt(0)
        Log.i("argumentsare", args.toString())

        when (commandId?.toInt()) {
            COMMAND_CREATE -> createFragment(root, reactNativeViewId)
        }
    }

    private fun setupLayout(view: View) {
        Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
            override fun doFrame(frameTimeNanos: Long) {
                manuallyLayoutChildren(view)
                view.viewTreeObserver.dispatchOnGlobalLayout()
                Choreographer.getInstance().postFrameCallback(this)
            }
        })
    }

    private fun manuallyLayoutChildren(view: View) {
        // propWidth and propHeight coming from react-native props
        val width = requireNotNull(propWidth)
        val height = requireNotNull(propHeight)

        view.measure(
            View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
        )

        view.layout(0, 0, width, height)
    }

    private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
        val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
        setupLayout(parentView)

        val myFragment = CenteredTextFragment()
        val activity = context.currentActivity as FragmentActivity
        activity.supportFragmentManager
            .beginTransaction()
            .replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
            .commit()
    }
}


class CenteredTextFragment: Fragment() {
    private lateinit var centeredText: CenteredText

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        super.onCreateView(inflater, container, savedInstanceState)
        centeredText = CenteredText(requireNotNull(context))
        return centeredText // this CustomView could be any view that you want to render
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

    }




    override fun onDestroy() {
        super.onDestroy()
    }
}


class CenteredTextPackage : ReactPackage {
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return Collections.singletonList(CenteredTextManager(reactContext))
    }

    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return emptyList()
    }
}

Full source code over here

1

There are 1 answers

1
phuocantd On

Try setting

com.facebook.react.bridge.UiThreadUtil#assertOnUiThread

before doing

FragmentTransaction.addToBackStack().

import com.facebook.react.bridge.UiThreadUtil;

if (UiThreadUtil.isOnUiThread()) {
    // do what you need to do here
}

Another potential solution is to override the onResume and onPause functions in your fragment and force them to call getReactInstanceManager().onHostResume(getActivity(), this); and getReactInstanceManager().onHostPause(getActivity()); respectively.

@Override 
public void onResume() {
   super.onResume();
   if (mReactInstanceManager != null) 
       mReactInstanceManager.onHostResume(getActivity(), this);
   }

@Override 
public void onPause() {
   super.onPause();
   if (mReactInstanceManager != null)
       mReactInstanceManager.onHostPause(getActivity());
   }

Alternatively, If you don't want to use Fabric, change the application onCreate method ReactNativeHost.getUseFabric() to return false.

@Override
protected boolean getUseFabric() {
   return false;
}