/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho; import android.content.Context; import android.content.ContextWrapper; import android.content.res.TypedArray; import android.support.annotation.AttrRes; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import com.facebook.litho.R; import com.facebook.infer.annotation.ThreadConfined; /** * A Context subclass for use within the Components framework. Contains extra bookkeeping * information used internally in the library. */ public class ComponentContext extends ContextWrapper { static final InternalNode NULL_LAYOUT = new NoOpInternalNode(); private final String mLogTag; private final ComponentsLogger mLogger; private final StateHandler mStateHandler; private String mNoStateUpdatesMethod; // Hold a reference to the component which scope we are currently within. private @ThreadConfined(ThreadConfined.ANY) Component<?> mComponentScope; private @ThreadConfined(ThreadConfined.ANY) ResourceCache mResourceCache; private @ThreadConfined(ThreadConfined.ANY) int mWidthSpec; private @ThreadConfined(ThreadConfined.ANY) int mHeightSpec; protected @ThreadConfined(ThreadConfined.ANY) TreeProps mTreeProps; private @ThreadConfined(ThreadConfined.ANY) ComponentTree mComponentTree; // Used to hold styling information applied to components private @ThreadConfined(ThreadConfined.ANY) @StyleRes int mDefStyleRes = 0; private @ThreadConfined(ThreadConfined.ANY) @AttrRes int mDefStyleAttr = 0; public ComponentContext(Context context) { this(context, null, null, null); } public ComponentContext(Context context, StateHandler stateHandler) { this(context, null, null, stateHandler); } /** * Constructor that can be used to receive log data from components. * Check {@link ComponentsLogger} for the type of events you can listen for. * * @param context Android context. * @param logTag Specify a log tag, to be used with the logger. * @param logger Specify the lifecycle logger to be used. */ public ComponentContext(Context context, String logTag, ComponentsLogger logger) { this(context, logTag, logger, null); } private ComponentContext( Context context, String logTag, ComponentsLogger logger, StateHandler stateHandler) { super((context instanceof ComponentContext) ? ((ComponentContext) context).getBaseContext() : context); if (logger != null && logTag == null) { throw new IllegalStateException("When a ComponentsLogger is set, a LogTag must be set"); } final ComponentContext componentContext = (context instanceof ComponentContext) ? (ComponentContext) context : null; final boolean transferLogging = (componentContext != null && logTag == null && logger == null); final boolean transferStateHandler = (componentContext != null && stateHandler == null); if (componentContext != null) { mTreeProps = componentContext.mTreeProps; mResourceCache = componentContext.mResourceCache; mWidthSpec = componentContext.mWidthSpec; mHeightSpec = componentContext.mHeightSpec; mComponentScope = componentContext.mComponentScope; mComponentTree = componentContext.mComponentTree; } else { mResourceCache = ResourceCache.getLatest(context.getResources().getConfiguration()); } mLogger = transferLogging ? componentContext.mLogger : logger; mLogTag = transferLogging ? componentContext.mLogTag : logTag; mStateHandler = transferStateHandler ? componentContext.mStateHandler : stateHandler; } static ComponentContext withComponentTree( ComponentContext context, ComponentTree componentTree) { ComponentContext componentContext = new ComponentContext( context, ComponentsPools.acquireStateHandler()); componentContext.mComponentTree = componentTree; return componentContext; } /** * Creates a new ComponentContext instance scoped to the given component and sets it on the * component. * @param context context scoped to the parent component * @param scope component associated with the newly created scoped context * @return a new ComponentContext instance scoped to the given component */ static ComponentContext withComponentScope(ComponentContext context, Component scope) { ComponentContext componentContext = context.makeNewCopy(); componentContext.mComponentScope = scope; componentContext.mComponentTree = context.mComponentTree; return componentContext; } ComponentContext makeNewCopy() { return new ComponentContext(this); } public Component getComponentScope() { return mComponentScope; } /** * Notify the Component Tree that it needs to synchronously perform a state update. * @param stateUpdate state update to perform */ public void updateState(ComponentLifecycle.StateUpdate stateUpdate) { checkIfNoStateUpdatesMethod(); if (mComponentTree == null) { return; } mComponentTree.updateState(mComponentScope.getGlobalKey(), stateUpdate); } /** * Notify the Component Tree that it needs to asynchronously perform a state update. * @param stateUpdate state update to perform */ public void updateStateAsync(ComponentLifecycle.StateUpdate stateUpdate) { checkIfNoStateUpdatesMethod(); if (mComponentTree == null) { return; } mComponentTree.updateStateAsync(mComponentScope.getGlobalKey(), stateUpdate); } public void updateStateLazy(ComponentLifecycle.StateUpdate stateUpdate) { if (mComponentTree == null) { return; } mComponentTree.updateStateLazy(mComponentScope.getGlobalKey(), stateUpdate); } public void enterNoStateUpdatesMethod(String noStateUpdatesMethod) { mNoStateUpdatesMethod = noStateUpdatesMethod; } public void exitNoStateUpdatesMethod() { mNoStateUpdatesMethod = null; } private void checkIfNoStateUpdatesMethod() { if (mNoStateUpdatesMethod != null) { throw new IllegalStateException( "Updating the state of a component during " + mNoStateUpdatesMethod + " leads to unexpected behaviour, consider using lazy state updates."); } } void setDefStyle(@AttrRes int defStyleAttr, @StyleRes int defStyleRes) { mDefStyleAttr = defStyleAttr; mDefStyleRes = defStyleRes; } public TypedArray obtainStyledAttributes(int[] attrs, @AttrRes int defStyleAttr) { return obtainStyledAttributes( null, attrs, defStyleAttr != 0 ? defStyleAttr : mDefStyleAttr, mDefStyleRes); } public String getLogTag() { return mLogTag; } public @Nullable ComponentsLogger getLogger() { return mLogger; } ComponentTree getComponentTree() { return mComponentTree; } protected void setTreeProps(TreeProps treeProps) { mTreeProps = treeProps; } protected @Nullable TreeProps getTreeProps() { return mTreeProps; } public ResourceCache getResourceCache() { return mResourceCache; } EventHandler newEventHandler(int id) { return new EventHandler(mComponentScope, id); } <E> EventHandler<E> newEventHandler(int id, Object[] params) { return new EventHandler<E>(mComponentScope, id, params); } InternalNode newLayoutBuilder(ComponentContext c) { return newLayoutBuilder(0, 0); } ComponentLayout.Builder newLayoutBuilder(Component<?> component) { return newLayoutBuilder(component, 0, 0); } ComponentLayout.Builder newLayoutBuilder(Component.Builder componentBuilder) { return newLayoutBuilder(componentBuilder.build(), 0, 0); } InternalNode newLayoutBuilder( @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { final InternalNode node = ComponentsPools.acquireInternalNode(this, getResources()); applyStyle(node, defStyleAttr, defStyleRes); return node; } ComponentLayout.Builder newLayoutBuilder( Component<?> component, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { component.applyStateUpdates(this); final InternalNode node = (InternalNode) component.getLifecycle().createLayout( component.getScopedContext(), component, false); component.getScopedContext().setTreeProps(null); if (node != NULL_LAYOUT) { applyStyle(node, defStyleAttr, defStyleRes); } return node; } ComponentLayout.Builder newLayoutBuilder( Component.Builder componentBuilder, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { return newLayoutBuilder(componentBuilder.build(), defStyleAttr, defStyleRes); } int getWidthSpec() { return mWidthSpec; } void setWidthSpec(int widthSpec) { mWidthSpec = widthSpec; } int getHeightSpec() { return mHeightSpec; } void setHeightSpec(int heightSpec) { mHeightSpec = heightSpec; } StateHandler getStateHandler() { return mStateHandler; } private void applyStyle(InternalNode node, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { if (defStyleAttr != 0 || defStyleRes != 0) { setDefStyle(defStyleAttr, defStyleRes); final TypedArray typedArray = obtainStyledAttributes( null, R.styleable.ComponentLayout, defStyleAttr, defStyleRes); node.applyAttributes(typedArray); typedArray.recycle(); setDefStyle(0, 0); } } }