{"id":409786,"date":"2024-06-29T21:00:07","date_gmt":"2024-06-29T21:00:07","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=409786"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=409786","title":{"rendered":"<span>Android: form builder \u043d\u0430 \u0441\u0442\u0430\u0440\u044b\u0445 \u0434\u043e\u0431\u0440\u044b\u0445 View<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/6ab\/985\/e8a\/6ab985e8a8535e847136b1240b1a3c23.png\" width=\"214\" height=\"477\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6ab\/985\/e8a\/6ab985e8a8535e847136b1240b1a3c23.png\"\/><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442 \u0432\u0441\u0435\u043c! \u0425\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u0441\u044f \u0438\u0434\u0435\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f <strong>form builder<\/strong>-\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0437\u0430\u0434. <\/p>\n<p>\u0412 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u043f\u0438\u0441\u0430\u043b \u043c\u043e\u0434\u0443\u043b\u044c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430 \u043f\u043b\u0430\u0442\u0435\u0436\u0438. \u041f\u043e \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0440\u0430\u0441\u0447\u0435\u0442\u0430\u043c  \u043c\u043e\u0434\u0443\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u043b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 300 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439, \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u043e 10 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0442.\u0435. \u044d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 3000 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432. \u042f \u0442\u043e\u0433\u0434\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b jetpack compose \u0438 \u043e\u0442 \u043c\u044b\u0441\u043b\u0438, \u0447\u0442\u043e \u043c\u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u201cView-based layouts\u201d xml \u0444\u0430\u0439\u043b\u043e\u0432 (\u0430 \u043f\u043e\u0442\u043e\u043c \u0438\u0445 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u0442\u044c \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c) \u043c\u043d\u0435 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u043e\u0441\u044c \u043a\u0430\u043a-\u0442\u043e \u043d\u0435 \u043f\u043e \u0441\u0435\u0431\u0435. <\/p>\n<p>\u041c\u043d\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c <strong>form builder<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u0431\u044b \u043b\u0435\u0433\u043a\u043e \u0438 \u0432 \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u0430\u043d\u0435\u0440\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u044b, \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0434\u043d\u043e\u0442\u0438\u043f\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043b\u0435\u0433\u043a\u043e \u0432\u043d\u043e\u0441\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u043d\u0435\u0447\u043d\u043e jetpack compose \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0438\u0447\u044c \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438, \u043d\u043e \u0431\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043e \u0442\u0435\u043c \u0438\u043b\u0438 \u0438\u043d\u044b\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u0432\u044b \u043e\u0441\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043d\u0430 \u0441\u0442\u0430\u0440\u043e\u043c \u0434\u043e\u0431\u0440\u043e\u043c View UI \u0438 \u0438\u0434\u0435\u044f \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e <strong>builder<\/strong>-\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0434\u043b\u044f \u0432\u0430\u0441 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0430.<\/p>\n<p>\u0418\u0442\u0430\u043a \u043f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043c\u043d\u0435 \u0431\u044b\u043b\u043e \u043d\u0443\u0436\u043d\u043e \u2013 \u044d\u0442\u043e \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442\u044c xml \u0444\u0430\u0439\u043b\u044b \u0442\u044b\u0441\u044f\u0447\u0430\u043c\u0438. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435, \u0445\u043e\u0440\u043e\u0448\u043e \u0431\u044b \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u0444\u0430\u0439\u043b \u0444\u043e\u0440\u043c\u044b \u0438 \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0444\u043e\u0440\u043c\u044b \u0431\u044b\u043b\u0438 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0445\u043e\u0436\u0438 \u0434\u0440\u0443\u0433 \u043d\u0430 \u0434\u0440\u0443\u0433\u0430: \u043d\u0430\u0431\u043e\u0440 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0438 \u0432\u043d\u0438\u0437\u0443 \u0444\u043e\u0440\u043c\u044b \u043a\u043d\u043e\u043f\u043a\u0430 \u0442\u0438\u043f\u0430 \u201csubmit form\u201d (\u0438\u043d\u043e\u0433\u0434\u0430 \u0441 \u043a\u0430\u043a\u0438\u043c\u0438-\u0442\u043e \u043f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438 \/ \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043f\u043e\u0434 \u043d\u0435\u0439). \u0420\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c RecyclerView, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0443\u0436\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432. \u041a\u0430\u043a-\u0442\u043e \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b xml \u0444\u0430\u0439\u043b \u0444\u043e\u0440\u043c\u044b:<\/p>\n<pre><code class=\"kotlin\">\u2026     &lt;data>         &lt;variable name=\"vm\" type=\"\u2026BaseViewModel\" \/>     &lt;\/data>      &lt;androidx.constraintlayout.widget.ConstraintLayout         android:layout_width=\"match_parent\"         android:layout_height=\"match_parent\" >          &lt;androidx.recyclerview.widget.RecyclerView             android:id=\"@+id\/rv_form\"             android:layout_width=\"0dp\"             android:layout_height=\"0dp\"             app:adapter=\"@{vm.rvMainAdapter}\"             app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"             app:listData=\"@{vm.mainItems}\"             \u2026          \/>          &lt;androidx.recyclerview.widget.RecyclerView             android:id=\"@+id\/rv_bottom\"             android:layout_width=\"0dp\"             android:layout_height=\"wrap_content\"             android:layout_marginBottom=\"@dimen\/ps_button_bottom_margin\"             app:adapter=\"@{vm.rvBottomAdapter}\"             app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"             app:listData=\"@{vm.bottomItems}\"             \u2026          \/>          &lt;ProgressBar \u2026<\/code><\/pre>\n<p>RecyclerView \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u043e\u0441\u044c \u0434\u0432\u0430, \u043e\u0434\u0438\u043d \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0444\u043e\u0440\u043c\u044b \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \/ \u043a\u043d\u043e\u043f\u043e\u043a \u0432\u043d\u0438\u0437\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u043d\u0435\u0439 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e data binding-\u0430 \u0444\u043e\u0440\u043c\u0430 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u0441 ViewModel \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u0430 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438 \u0432\u0441\u0435 \u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044d\u043b-\u0442\u044b \u0431\u044b\u043b\u0438 \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044b \u0432 LiveData).<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 BaseViewModel:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseViewModel(appContextProvider: AppContextProvider) : ViewModel() {     val rvMainAdapter = MutableLiveData&lt;ListAdapter&lt;*, *>>(ViewsAdapter())     val rvBottomAdapter = MutableLiveData&lt;ListAdapter&lt;*, *>>(ViewsAdapter())     val mainItems = MutableLiveData&lt;List&lt;BaseFieldValue>>()     val bottomItems = MutableLiveData&lt;List&lt;BaseFieldValue>>() \u2026 }  @BindingAdapter(\"adapter\") internal fun setRecyclerViewAdapter(recyclerView: RecyclerView, adapter: ListAdapter&lt;*, *>) {     recyclerView.adapter = adapter }  @BindingAdapter(\"listData\") internal fun bindRecyclerView(recyclerView: RecyclerView, data: List&lt;BaseFieldValue>?) {     (recyclerView.adapter as ListAdapter&lt;BaseFieldValue, *>).submitList(data) }<\/code><\/pre>\n<p>\u0412 \u043d\u0435\u043c \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0438 \u0441\u043f\u0438\u0441\u043a\u0438 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f RecyclerView, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u201c\u0431\u0430\u0439\u043d\u0434\u044f\u0442\u044c\u0441\u044f\u201d \u043a \u0444\u043e\u0440\u043c\u0435.<\/p>\n<p>\u00a0\u0422\u0435\u043f\u0435\u0440\u044c \u043e \u0441\u0430\u043c\u0438\u0445 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0445. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0439 xml \u0444\u0430\u0439\u043b. \u041f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 InputView:<\/p>\n<pre><code class=\"kotlin\">&lt;data>     &lt;variable name=\"fv\" type=\"\u2026InputFieldValue\" \/> &lt;\/data>  &lt;...InputView     android:id=\"@+id\/item\"     android:layout_width=\"match_parent\"     android:layout_height=\"wrap_content\"     app:design_label=\"@{fv.title}\"     app:design_hint=\"@{fv.hint}\"     app:design_assistiveText=\"@{fv.assistive}\"     app:design_text=\"@={fv.text}\"     app:error_text=\"@{fv.error}\"     app:design_icon=\"@{fv.endDrawableRes}\"     app:design_iconColor=\"@{fv.endDrawableColorRes}\"     app:inputType=\"@{fv.inputType}\"     app:imeOptions=\"@{fv.imeOptions}\"     app:enabled=\"@{fv.enabled}\"     app:onIconClickListener=\"@{() -> fv.onIconClick()}\"     app:fv=\"@{fv}\" \/> <\/code><\/pre>\n<p>InputView \u2013 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438\u0437 \u0434\u0438\u0437\u0430\u0439\u043d-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f (\u201c\u0431\u0430\u0439\u043d\u0434\u044f\u0442\u044c\u0441\u044f\u201d) \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 FieldValues:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseFieldValue(     open val id: Int,     val type: ViewType,     var containerId: Int? = null,     var isVisible: Boolean = true,     open var data: Any? = null, ) {     abstract fun bind(parent: ViewGroup, binding: ViewDataBinding)      override fun equals(other: Any?): Boolean {\u2026}      override fun hashCode(): Int {\u2026} }  internal data class InputFieldValue(     @IdRes override val id: Int,     val title: String? = null,     val text: MutableLiveData&lt;String?> = MutableLiveData(null),     var error: MutableLiveData&lt;String> = MutableLiveData(\"\"),     val inputType: String = INPUT_TYPE_NUMBER,     val imeOptions: String = IME_ACTION_NEXT,     val hint: String? = null,     val assistive: String? = null,     @DrawableRes val endDrawableRes: Int = 0,     @ColorRes val endDrawableColorRes: Int = 0,     var enabled: Boolean = true,     val formatter: BaseInputFieldFormatter = BaseInputFieldFormatter(),     val validator: BaseInputFieldValidator = BaseInputFieldValidator(),     val action: ((data: Any?) -> Unit)? = null,     override var data: Any? = null,     val onIconClickAction: ((data: Any?) -> Unit)? = null, ) : BaseFieldValue(id, ViewType.INPUT) {      override fun bind(parent: ViewGroup, binding: ViewDataBinding) {         (binding as PsItemInputViewBinding).fv = this     }      fun onIconClick() {         onIconClickAction?.invoke(data)     } }<\/code><\/pre>\n<p>\u041f\u043e\u0432\u0442\u043e\u0440\u044e, \u0447\u0442\u043e InputView \u0441\u0430\u043c\u044b\u0439 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u0434\u0440\u0443\u0433\u0438\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0449\u0435. \u0424\u0443\u043d\u043a\u0446\u0438\u044f bind \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 xml \u0444\u0430\u0439\u043b\u043e\u043c. \u0414\u043b\u044f InputView \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f formatter \u0438 validator \u0434\u043b\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0436\u0434\u044b\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u043e\u0431\u0435\u043b \u0438\u043b\u0438 \u0442\u0438\u0440\u0435 \u0438 \u043d\u0435 \u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0446\u0438\u0444\u0440, \u0430 \u043d\u043e\u043c\u0435\u0440 \u043a\u0430\u0440\u0442\u044b \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 \u041b\u0443\u0301\u043d\u0430).<\/p>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u044d\u0442\u043e ContainerView. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e (RecyclerView \u0443 \u043c\u0435\u043d\u044f \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0430\u044f, \u0442.\u0435. \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0442 \u0434\u0440\u0443\u0433 \u0437\u0430 \u0434\u0440\u0443\u0433\u043e\u043c \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437, \u0430 \u0438\u043d\u043e\u0433\u0434\u0430 \u0432 \u0444\u043e\u0440\u043c\u0435 \u043d\u0430\u0434\u043e \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e).<\/p>\n<pre><code>&lt;data>     &lt;variable name=\"fv\" type=\"...ContainerFieldValue\" \/> &lt;\/data>  &lt;LinearLayout     android:id=\"@+id\/container\"     android:layout_width=\"match_parent\"     android:layout_height=\"wrap_content\"     android:orientation=\"horizontal\" \/>  ----  internal data class ContainerFieldValue(     @IdRes override val id: Int = View.generateViewId(),     val items: List&lt;BaseFieldValue> ) : BaseFieldValue(id, ViewType.CONTAINER) {      override fun bind(parent: ViewGroup, _binding: ViewDataBinding) {         val binding = _binding as PsItemContainerViewBinding         binding.fv = this         val view = binding.root as ViewGroup          items.forEach { field ->             val viewHolder = createViewHolderByType(parent, field.type.ordinal)             viewHolder.bind(field)             view.addView(viewHolder.itemView)             val lp = viewHolder.itemView.layoutParams as LinearLayout.LayoutParams             lp.weight = 1.0f             view.layoutParams = lp         }     } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c container, \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 ViewHolder, \u201c\u0431\u0430\u0439\u043d\u0434\u0438\u0442\u201d \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 container.<\/p>\n<p>\u00a0\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0435\u0441\u0442\u044c xml \u0444\u0430\u0439\u043b \u0438 \u043a\u043b\u0430\u0441\u0441 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u043e\u043b\u044f \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b \u0447\u0435\u0440\u0435\u0437 data binding, \u043d\u0430\u0441\u0442\u0430\u043b\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u0430. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 ViewHolder:<\/p>\n<pre><code class=\"kotlin\">internal class ViewHolder&lt;Binding : ViewDataBinding>(     resId: Int,     val parent: ViewGroup,     var binding: Binding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), resId, parent, false) ) : RecyclerView.ViewHolder(binding.root) {      init {         val fragment = parent.findFragment&lt;Fragment>()          with(binding) {             lifecycleOwner = fragment.viewLifecycleOwner         }     }      fun bind(item: BaseFieldValue) {         item.bind(parent, binding)     } }<\/code><\/pre>\n<p>\u041e\u043d \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 lifecycleOwner \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e bind \u0434\u043b\u044f FieldValue.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e ViewsAdapter \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u043c:<\/p>\n<pre><code class=\"kotlin\">internal class ViewsAdapter : ListAdapter&lt;BaseFieldValue, ViewHolder&lt;out ViewDataBinding>>(DiffCalback) {     object DiffCalback : DiffUtil.ItemCallback&lt;BaseFieldValue>() {         override fun areItemsTheSame(oldItem: BaseFieldValue, newItem: BaseFieldValue): Boolean {             return oldItem.id == newItem.id         }          override fun areContentsTheSame(oldItem: BaseFieldValue, newItem: BaseFieldValue): Boolean {             return oldItem == newItem         }     }      override fun getItemViewType(position: Int): Int {         return getItem(position).type.ordinal     }      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder&lt;out ViewDataBinding> {         return createViewHolderByType(parent, viewType)     }      override fun onBindViewHolder(holder: ViewHolder&lt;out ViewDataBinding>, position: Int) {         holder.bind(getItem(position))     } }  internal enum class ViewType {     INPUT, CONTAINER, \u2026 }  internal fun createViewHolderByType(parent: ViewGroup, viewType: Int): ViewHolder&lt;out ViewDataBinding> {     return when (viewType) {         ViewType.INPUT.ordinal -> ViewHolder&lt;PsItemInputViewBinding>(R.layout.ps_item_input_view, parent)         \u2026    } }<\/code><\/pre>\n<p>\u041d\u0443 \u0438 BaseFragment, \u043e\u0442 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0442\u0441\u044f \u0432\u0441\u0435 Fragment-\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseFragment&lt;Binding: ViewDataBinding, WM: BaseViewModel> : Fragment() {     protected abstract val layoutRes: Int     private var _binding: Binding? = null     val binding         get() = _binding!!     protected abstract val viewModel: WM     \u2026     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {         _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) ?: throw Exception(getString(R.string.ps_error_layout_id))              with(binding) {             lifecycleOwner = viewLifecycleOwner             setVariable(BR.vm, viewModel)         }              viewModel.onCreateView()                  viewModel.action.observe(viewLifecycleOwner) {\u2026}         \u2026         return binding.root     }     \u2026 }<\/code><\/pre>\n<p>ViewModel \u043d\u0443\u0436\u043d\u0430 \u0437\u0434\u0435\u0441\u044c \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u0436\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u0438 \u0434\u043b\u044f \u201cobserve\u201d \u0432\u0441\u0435\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0433\u043e.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u044d\u0442\u043e \u0432\u0441\u044f \u043e\u0431\u0432\u044f\u0437\u043a\u0430. \u0414\u0430\u043b\u044c\u0448\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f Fragment \u0438 ViewModel. Fragment \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439:<\/p>\n<pre><code class=\"kotlin\">internal class FineSearchFragment : BaseFragment&lt;PsFragmentFormBinding, FineSearchViewModel>() {     override val viewModel: FineSearchViewModel by viewModels()     override val layoutRes = R.layout.ps_fragment_form }<\/code><\/pre>\n<p>\u0412\u043e ViewModel \u043c\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u044d\u043a\u0440\u0430\u043d\u0430. \u0412\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0438\u043f\u0438\u0447\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0430:<\/p>\n<pre><code class=\"kotlin\">internal class FineSearchViewModel @Inject constructor(     private val repository: ChargesRepository,     appContextProvider: AppContextProvider, ) : BaseViewModel(appContextProvider) {      override fun onCreate() {         super.onCreate()          val chargesData = repository.getData()          with(context.resources) {             addFields(listOf(                 InputFieldValue(                     id = R.id.ps_...,                     title = getString(R.string.ps_...),                     hint = getString(R.string.ps_...),                     text = MutableLiveData(\u2026),                     inputType = INPUT_TYPE_TEXT,                     formatter = TemplateFieldFormatter(AUTO_DOCUMENTS_TEMPLATE),                     validator = NumberOrEmptyFieldValidator(VRC_LENGTH),                     endDrawableRes = R.drawable\u2026,                     endDrawableColorRes = R.color\u2026,                     onIconClickAction = ::showAdvice,                     data = VRC_ADVICE,                 ),                 CheckboxFieldValue(                     id = R.id.ps_...,                     title = getString(R.string.ps_...),                     selected = MutableLiveData(chargesData.saveDocuments),                 ),             ))              addField(                 ContainerFieldValue(                     id = R.id.ps_container1,                     items = listOf(                         ButtonFieldValue(                             text = MutableLiveData(getString(R.string.ps_.)),                             horizontalMarginRes = R.dimen.ps_...,                             action = ::onCancelButtonClick,                         ),                         ButtonFieldValue(                             text = MutableLiveData(getString(R.string.ps_.)),                             horizontalMarginRes = R.dimen.ps_...,                             action = ::onOkButtonClick,                         ),                     ),                 ),                 isMainFields = false             )         }     } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c addField \/ addFields \u0444-\u0446\u0438\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432 \u0441\u043f\u0438\u0441\u043e\u043a mainItems \/ bottomItems. \u0414\u0430\u043b\u0435\u0435 \u0432\u043e ViewModel \u0434\u0435\u043b\u0430\u044e\u0442\u0441\u044f \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b, \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \/ \u0430\u043f\u0434\u0435\u0439\u0442\u044f\u0442\u0441\u044f \u043f\u043e\u043b\u044f \u0444\u043e\u0440\u043c\u044b \u0438 \u0442.\u0434.<\/p>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0442\u043e\u0433\u043e, \u0447\u0435\u0433\u043e \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c: \u044d\u043a\u0440\u0430\u043d \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u0430\u043d\u0435\u0440\u0435 (\u0441\u043f\u0438\u0441\u043a\u043e\u043c data class-\u043e\u0432 \u0441 \u043d\u0443\u0436\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438), \u0435\u0441\u0442\u044c \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d xml \u0444\u0430\u0439\u043b \u0444\u043e\u0440\u043c\u044b \u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e xml \u0444\u0430\u0439\u043b\u043e\u0432 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u0444\u0430\u0439\u043b\u044b. <\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c \u0438\u0434\u0435\u044f \u0441\u043c\u043e\u0436\u0435\u0442 \u043a\u043e\u043c\u0443-\u0442\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/825554\/\"> https:\/\/habr.com\/ru\/articles\/825554\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"\"><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442 \u0432\u0441\u0435\u043c! \u0425\u043e\u0447\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u0441\u044f \u0438\u0434\u0435\u0435\u0439 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f <strong>form builder<\/strong>-\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0437\u0430\u0434. <\/p>\n<p>\u0412 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u043f\u0438\u0441\u0430\u043b \u043c\u043e\u0434\u0443\u043b\u044c, \u043e\u0442\u0432\u0435\u0447\u0430\u044e\u0449\u0438\u0439 \u0437\u0430 \u043f\u043b\u0430\u0442\u0435\u0436\u0438. \u041f\u043e \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0440\u0430\u0441\u0447\u0435\u0442\u0430\u043c  \u043c\u043e\u0434\u0443\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u043b \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 300 \u043f\u043b\u0430\u0442\u0435\u0436\u0435\u0439, \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u043b\u0430\u0442\u0435\u0436 \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u043e 10 \u044d\u043a\u0440\u0430\u043d\u043e\u0432, \u0442.\u0435. \u044d\u0442\u043e \u0431\u043e\u043b\u0435\u0435 3000 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u044d\u043a\u0440\u0430\u043d\u043e\u0432. \u042f \u0442\u043e\u0433\u0434\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b jetpack compose \u0438 \u043e\u0442 \u043c\u044b\u0441\u043b\u0438, \u0447\u0442\u043e \u043c\u043d\u0435 \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u201cView-based layouts\u201d xml \u0444\u0430\u0439\u043b\u043e\u0432 (\u0430 \u043f\u043e\u0442\u043e\u043c \u0438\u0445 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u0442\u044c \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c) \u043c\u043d\u0435 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u043e\u0441\u044c \u043a\u0430\u043a-\u0442\u043e \u043d\u0435 \u043f\u043e \u0441\u0435\u0431\u0435. <\/p>\n<p>\u041c\u043d\u0435 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0438\u043b\u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c <strong>form builder<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u0431\u044b \u043b\u0435\u0433\u043a\u043e \u0438 \u0432 \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u0430\u043d\u0435\u0440\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u044b, \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442\u044c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0434\u043d\u043e\u0442\u0438\u043f\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043b\u0435\u0433\u043a\u043e \u0432\u043d\u043e\u0441\u0438\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u043d\u0435\u0447\u043d\u043e jetpack compose \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0438\u0447\u044c \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438, \u043d\u043e \u0431\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u043f\u043e \u0442\u0435\u043c \u0438\u043b\u0438 \u0438\u043d\u044b\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c \u0432\u044b \u043e\u0441\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043d\u0430 \u0441\u0442\u0430\u0440\u043e\u043c \u0434\u043e\u0431\u0440\u043e\u043c View UI \u0438 \u0438\u0434\u0435\u044f \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e <strong>builder<\/strong>-\u0430 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0434\u043b\u044f \u0432\u0430\u0441 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u0430.<\/p>\n<p>\u0418\u0442\u0430\u043a \u043f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043c\u043d\u0435 \u0431\u044b\u043b\u043e \u043d\u0443\u0436\u043d\u043e \u2013 \u044d\u0442\u043e \u043d\u0435 \u043f\u043b\u043e\u0434\u0438\u0442\u044c xml \u0444\u0430\u0439\u043b\u044b \u0442\u044b\u0441\u044f\u0447\u0430\u043c\u0438. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435, \u0445\u043e\u0440\u043e\u0448\u043e \u0431\u044b \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u0444\u0430\u0439\u043b \u0444\u043e\u0440\u043c\u044b \u0438 \u043d\u0430\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0444\u043e\u0440\u043c\u044b \u0431\u044b\u043b\u0438 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0445\u043e\u0436\u0438 \u0434\u0440\u0443\u0433 \u043d\u0430 \u0434\u0440\u0443\u0433\u0430: \u043d\u0430\u0431\u043e\u0440 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0447\u0438\u0441\u043b\u0430 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0438 \u0432\u043d\u0438\u0437\u0443 \u0444\u043e\u0440\u043c\u044b \u043a\u043d\u043e\u043f\u043a\u0430 \u0442\u0438\u043f\u0430 \u201csubmit form\u201d (\u0438\u043d\u043e\u0433\u0434\u0430 \u0441 \u043a\u0430\u043a\u0438\u043c\u0438-\u0442\u043e \u043f\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u044f\u043c\u0438 \/ \u0441\u0441\u044b\u043b\u043a\u0430\u043c\u0438 \u043f\u043e\u0434 \u043d\u0435\u0439). \u0420\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c RecyclerView, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043d\u0443\u0436\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432. \u041a\u0430\u043a-\u0442\u043e \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b xml \u0444\u0430\u0439\u043b \u0444\u043e\u0440\u043c\u044b:<\/p>\n<pre><code class=\"kotlin\">\u2026     &lt;data>         &lt;variable name=\"vm\" type=\"\u2026BaseViewModel\" \/>     &lt;\/data>      &lt;androidx.constraintlayout.widget.ConstraintLayout         android:layout_width=\"match_parent\"         android:layout_height=\"match_parent\" >          &lt;androidx.recyclerview.widget.RecyclerView             android:id=\"@+id\/rv_form\"             android:layout_width=\"0dp\"             android:layout_height=\"0dp\"             app:adapter=\"@{vm.rvMainAdapter}\"             app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"             app:listData=\"@{vm.mainItems}\"             \u2026          \/>          &lt;androidx.recyclerview.widget.RecyclerView             android:id=\"@+id\/rv_bottom\"             android:layout_width=\"0dp\"             android:layout_height=\"wrap_content\"             android:layout_marginBottom=\"@dimen\/ps_button_bottom_margin\"             app:adapter=\"@{vm.rvBottomAdapter}\"             app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"             app:listData=\"@{vm.bottomItems}\"             \u2026          \/>          &lt;ProgressBar \u2026<\/code><\/pre>\n<p>RecyclerView \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u043e\u0441\u044c \u0434\u0432\u0430, \u043e\u0434\u0438\u043d \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0444\u043e\u0440\u043c\u044b \u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \/ \u043a\u043d\u043e\u043f\u043e\u043a \u0432\u043d\u0438\u0437\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441 \u043d\u0435\u0439 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432. \u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e data binding-\u0430 \u0444\u043e\u0440\u043c\u0430 \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u043b\u0430\u0441\u044c \u0441 ViewModel \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u0430 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438 \u0432\u0441\u0435 \u0438\u0445 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f (\u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044d\u043b-\u0442\u044b \u0431\u044b\u043b\u0438 \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044b \u0432 LiveData).<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 BaseViewModel:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseViewModel(appContextProvider: AppContextProvider) : ViewModel() {     val rvMainAdapter = MutableLiveData&lt;ListAdapter&lt;*, *>>(ViewsAdapter())     val rvBottomAdapter = MutableLiveData&lt;ListAdapter&lt;*, *>>(ViewsAdapter())     val mainItems = MutableLiveData&lt;List&lt;BaseFieldValue>>()     val bottomItems = MutableLiveData&lt;List&lt;BaseFieldValue>>() \u2026 }  @BindingAdapter(\"adapter\") internal fun setRecyclerViewAdapter(recyclerView: RecyclerView, adapter: ListAdapter&lt;*, *>) {     recyclerView.adapter = adapter }  @BindingAdapter(\"listData\") internal fun bindRecyclerView(recyclerView: RecyclerView, data: List&lt;BaseFieldValue>?) {     (recyclerView.adapter as ListAdapter&lt;BaseFieldValue, *>).submitList(data) }<\/code><\/pre>\n<p>\u0412 \u043d\u0435\u043c \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u044b \u0438 \u0441\u043f\u0438\u0441\u043a\u0438 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f RecyclerView, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u201c\u0431\u0430\u0439\u043d\u0434\u044f\u0442\u044c\u0441\u044f\u201d \u043a \u0444\u043e\u0440\u043c\u0435.<\/p>\n<p>\u00a0\u0422\u0435\u043f\u0435\u0440\u044c \u043e \u0441\u0430\u043c\u0438\u0445 UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0445. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0439 xml \u0444\u0430\u0439\u043b. \u041f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 InputView:<\/p>\n<pre><code class=\"kotlin\">&lt;data>     &lt;variable name=\"fv\" type=\"\u2026InputFieldValue\" \/> &lt;\/data>  &lt;...InputView     android:id=\"@+id\/item\"     android:layout_width=\"match_parent\"     android:layout_height=\"wrap_content\"     app:design_label=\"@{fv.title}\"     app:design_hint=\"@{fv.hint}\"     app:design_assistiveText=\"@{fv.assistive}\"     app:design_text=\"@={fv.text}\"     app:error_text=\"@{fv.error}\"     app:design_icon=\"@{fv.endDrawableRes}\"     app:design_iconColor=\"@{fv.endDrawableColorRes}\"     app:inputType=\"@{fv.inputType}\"     app:imeOptions=\"@{fv.imeOptions}\"     app:enabled=\"@{fv.enabled}\"     app:onIconClickListener=\"@{() -> fv.onIconClick()}\"     app:fv=\"@{fv}\" \/> <\/code><\/pre>\n<p>InputView \u2013 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438\u0437 \u0434\u0438\u0437\u0430\u0439\u043d-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u044e\u0442\u0441\u044f (\u201c\u0431\u0430\u0439\u043d\u0434\u044f\u0442\u044c\u0441\u044f\u201d) \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>\u0412\u043e\u0442 \u0442\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 FieldValues:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseFieldValue(     open val id: Int,     val type: ViewType,     var containerId: Int? = null,     var isVisible: Boolean = true,     open var data: Any? = null, ) {     abstract fun bind(parent: ViewGroup, binding: ViewDataBinding)      override fun equals(other: Any?): Boolean {\u2026}      override fun hashCode(): Int {\u2026} }  internal data class InputFieldValue(     @IdRes override val id: Int,     val title: String? = null,     val text: MutableLiveData&lt;String?> = MutableLiveData(null),     var error: MutableLiveData&lt;String> = MutableLiveData(\"\"),     val inputType: String = INPUT_TYPE_NUMBER,     val imeOptions: String = IME_ACTION_NEXT,     val hint: String? = null,     val assistive: String? = null,     @DrawableRes val endDrawableRes: Int = 0,     @ColorRes val endDrawableColorRes: Int = 0,     var enabled: Boolean = true,     val formatter: BaseInputFieldFormatter = BaseInputFieldFormatter(),     val validator: BaseInputFieldValidator = BaseInputFieldValidator(),     val action: ((data: Any?) -> Unit)? = null,     override var data: Any? = null,     val onIconClickAction: ((data: Any?) -> Unit)? = null, ) : BaseFieldValue(id, ViewType.INPUT) {      override fun bind(parent: ViewGroup, binding: ViewDataBinding) {         (binding as PsItemInputViewBinding).fv = this     }      fun onIconClick() {         onIconClickAction?.invoke(data)     } }<\/code><\/pre>\n<p>\u041f\u043e\u0432\u0442\u043e\u0440\u044e, \u0447\u0442\u043e InputView \u0441\u0430\u043c\u044b\u0439 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u0434\u0440\u0443\u0433\u0438\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0441\u0438\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0449\u0435. \u0424\u0443\u043d\u043a\u0446\u0438\u044f bind \u0441\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 xml \u0444\u0430\u0439\u043b\u043e\u043c. \u0414\u043b\u044f InputView \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f formatter \u0438 validator \u0434\u043b\u044f \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0436\u0434\u044b\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u043e\u0431\u0435\u043b \u0438\u043b\u0438 \u0442\u0438\u0440\u0435 \u0438 \u043d\u0435 \u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0446\u0438\u0444\u0440, \u0430 \u043d\u043e\u043c\u0435\u0440 \u043a\u0430\u0440\u0442\u044b \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 \u041b\u0443\u0301\u043d\u0430).<\/p>\n<p>\u0415\u0449\u0435 \u043e\u0434\u0438\u043d \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u044d\u0442\u043e ContainerView. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e (RecyclerView \u0443 \u043c\u0435\u043d\u044f \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u0430\u044f, \u0442.\u0435. \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0442 \u0434\u0440\u0443\u0433 \u0437\u0430 \u0434\u0440\u0443\u0433\u043e\u043c \u0441\u0432\u0435\u0440\u0445\u0443 \u0432\u043d\u0438\u0437, \u0430 \u0438\u043d\u043e\u0433\u0434\u0430 \u0432 \u0444\u043e\u0440\u043c\u0435 \u043d\u0430\u0434\u043e \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e).<\/p>\n<pre><code>&lt;data>     &lt;variable name=\"fv\" type=\"...ContainerFieldValue\" \/> &lt;\/data>  &lt;LinearLayout     android:id=\"@+id\/container\"     android:layout_width=\"match_parent\"     android:layout_height=\"wrap_content\"     android:orientation=\"horizontal\" \/>  ----  internal data class ContainerFieldValue(     @IdRes override val id: Int = View.generateViewId(),     val items: List&lt;BaseFieldValue> ) : BaseFieldValue(id, ViewType.CONTAINER) {      override fun bind(parent: ViewGroup, _binding: ViewDataBinding) {         val binding = _binding as PsItemContainerViewBinding         binding.fv = this         val view = binding.root as ViewGroup          items.forEach { field ->             val viewHolder = createViewHolderByType(parent, field.type.ordinal)             viewHolder.bind(field)             view.addView(viewHolder.itemView)             val lp = viewHolder.itemView.layoutParams as LinearLayout.LayoutParams             lp.weight = 1.0f             view.layoutParams = lp         }     } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c container, \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 ViewHolder, \u201c\u0431\u0430\u0439\u043d\u0434\u0438\u0442\u201d \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 container.<\/p>\n<p>\u00a0\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e UI \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0435\u0441\u0442\u044c xml \u0444\u0430\u0439\u043b \u0438 \u043a\u043b\u0430\u0441\u0441 \u0434\u0430\u043d\u043d\u044b\u0445, \u0438 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u043e\u043b\u044f \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b \u0447\u0435\u0440\u0435\u0437 data binding, \u043d\u0430\u0441\u0442\u0430\u043b\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0430\u0434\u0430\u043f\u0442\u0435\u0440\u0430. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 ViewHolder:<\/p>\n<pre><code class=\"kotlin\">internal class ViewHolder&lt;Binding : ViewDataBinding>(     resId: Int,     val parent: ViewGroup,     var binding: Binding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), resId, parent, false) ) : RecyclerView.ViewHolder(binding.root) {      init {         val fragment = parent.findFragment&lt;Fragment>()          with(binding) {             lifecycleOwner = fragment.viewLifecycleOwner         }     }      fun bind(item: BaseFieldValue) {         item.bind(parent, binding)     } }<\/code><\/pre>\n<p>\u041e\u043d \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 lifecycleOwner \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e bind \u0434\u043b\u044f FieldValue.<\/p>\n<p>\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e ViewsAdapter \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u043c:<\/p>\n<pre><code class=\"kotlin\">internal class ViewsAdapter : ListAdapter&lt;BaseFieldValue, ViewHolder&lt;out ViewDataBinding>>(DiffCalback) {     object DiffCalback : DiffUtil.ItemCallback&lt;BaseFieldValue>() {         override fun areItemsTheSame(oldItem: BaseFieldValue, newItem: BaseFieldValue): Boolean {             return oldItem.id == newItem.id         }          override fun areContentsTheSame(oldItem: BaseFieldValue, newItem: BaseFieldValue): Boolean {             return oldItem == newItem         }     }      override fun getItemViewType(position: Int): Int {         return getItem(position).type.ordinal     }      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder&lt;out ViewDataBinding> {         return createViewHolderByType(parent, viewType)     }      override fun onBindViewHolder(holder: ViewHolder&lt;out ViewDataBinding>, position: Int) {         holder.bind(getItem(position))     } }  internal enum class ViewType {     INPUT, CONTAINER, \u2026 }  internal fun createViewHolderByType(parent: ViewGroup, viewType: Int): ViewHolder&lt;out ViewDataBinding> {     return when (viewType) {         ViewType.INPUT.ordinal -> ViewHolder&lt;PsItemInputViewBinding>(R.layout.ps_item_input_view, parent)         \u2026    } }<\/code><\/pre>\n<p>\u041d\u0443 \u0438 BaseFragment, \u043e\u0442 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0442\u0441\u044f \u0432\u0441\u0435 Fragment-\u044b \u044d\u043a\u0440\u0430\u043d\u043e\u0432:<\/p>\n<pre><code class=\"kotlin\">internal abstract class BaseFragment&lt;Binding: ViewDataBinding, WM: BaseViewModel> : Fragment() {     protected abstract val layoutRes: Int     private var _binding: Binding? = null     val binding        <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-409786","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/409786","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=409786"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/409786\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=409786"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=409786"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=409786"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}