{"id":466292,"date":"2025-07-07T15:29:59","date_gmt":"2025-07-07T15:29:59","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=466292"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=466292","title":{"rendered":"<span>\u0427\u0430\u0441\u0442\u044c 2. GoForm \u2014 \u043a\u0430\u043a \u043d\u0435 \u0441\u0442\u0440\u0430\u0434\u0430\u0442\u044c \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 \u0432\u043e Flutter<\/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<p>\u0420\u0430\u0434 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0430\u043c \u0441\u0432\u043e\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443\u00a0<a href=\"https:\/\/pub.dev\/packages\/go_form\" rel=\"noopener noreferrer nofollow\"><strong>GoForm<\/strong><\/a>\u00a0\u2014 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u043e \u0438\u0437 \u0431\u043e\u043b\u0438 \u0438 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439 \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 Flutter.<\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/922846\/\" rel=\"noopener noreferrer nofollow\">\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438, \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b Flutter \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (Form, TextFormField, GlobalKey) \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u0442\u0440\u0435\u0449\u0430\u0442\u044c \u043f\u043e \u0448\u0432\u0430\u043c \u043f\u0440\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041f\u043e\u043c\u043d\u0438\u0442\u0435 \u044d\u0442\u0438 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 TextEditingController, \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439 \u0438 \u0442\u0430\u043d\u0446\u044b \u0441 \u0431\u0443\u0431\u043d\u043e\u043c \u0432\u043e\u043a\u0440\u0443\u0433 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f?<\/p>\n<p>\u042f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u044d\u0442\u0438\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u043d\u0435 \u0440\u0430\u0437 \u0438 \u043d\u0435 \u0434\u0432\u0430, \u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434, \u0440\u0435\u0448\u0438\u043b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0437\u0431\u0430\u0432\u0438\u0442 \u0432\u0430\u0441 \u043e\u0442 \u044d\u0442\u0438\u0445 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439. \u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f <a href=\"https:\/\/pub.dev\/packages\/go_form\" rel=\"noopener noreferrer nofollow\">GoForm<\/a> <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c, \u043a\u0430\u043a GoForm \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0441\u0435 \u0442\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438, \u0438 \u0434\u0430\u0436\u0435 \u0431\u043e\u043b\u044c\u0448\u0435.<\/p>\n<p>\u0413\u043e\u0442\u043e\u0432\u044b \u0443\u0437\u043d\u0430\u0442\u044c, \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 \u0432\u043e Flutter \u043f\u0440\u0438\u044f\u0442\u043d\u043e\u0439 \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0439? \u0422\u043e\u0433\u0434\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0447\u043d\u0435\u043c!<\/p>\n<p>\u0418 \u0434\u0430, \u044d\u0442\u043e \u043c\u043e\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u2014 \u044f \u0437\u043d\u0430\u044e \u043e \u0435\u0451 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u0430\u043c\u043d\u044f\u0445 \u043d\u0435 \u043f\u043e\u043d\u0430\u0441\u043b\u044b\u0448\u043a\u0435 \u0438 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0435\u043c\u0438 \u0442\u043e\u043d\u043a\u043e\u0441\u0442\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f. \u0418 \u043a\u043e\u0441\u0442\u044b\u043b\u044f\u043c\u0438. <\/p>\n<h2>\u041f\u043e\u0447\u0435\u043c\u0443 GoForm?<\/h2>\n<p>GoForm \u2014 \u044d\u0442\u043e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u043e\u0435:<\/p>\n<ul>\n<li>\n<p>\u0423\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0440\u0443\u0442\u0438\u043d\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 (\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, \u0444\u043e\u043a\u0443\u0441, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435)<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 API \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439)<\/p>\n<\/li>\n<li>\n<p>\u041b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 Riverpod, Bloc, Provider \u0438 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u0434\u043d\u043e\u0433\u043e <code>FormController<\/code><\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0444\u0438\u0447\u0438 \u043a\u0430\u043a <code>debounce<\/code>, \u0441\u043a\u0440\u043e\u043b\u043b \u043a \u043e\u0448\u0438\u0431\u043a\u0430\u043c, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 Flutter:<\/strong><\/p>\n<ul>\n<li>\n<p>\u0415\u0434\u0438\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0438\u0441\u0430\u0442\u044c <code>TextEditingController<\/code> \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u043c\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0413\u0438\u0431\u043a\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<li>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<\/li>\n<\/ul>\n<p>\u26a0\ufe0f\u00a0<strong>\u0412\u0430\u0436\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435:<\/strong>\u00a0\u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430, \u0438 \u044d\u0442\u043e \u043a\u0440\u0443\u0442\u043e! \u041d\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u0435 \u0432 \u043c\u0430\u0440\u0430\u0444\u043e\u043d \u043f\u043e \u043f\u0443\u0441\u0442\u044b\u043d\u0435, \u044f \u0441\u043f\u0440\u044f\u0442\u0430\u043b \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043f\u043e\u0434 \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u044b.<\/p>\n<h2>\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435:<\/h2>\n<ul>\n<li>\n<p><a href=\"#custom_fields\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#basic_form\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u044b<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#initialValue\" rel=\"noopener noreferrer nofollow\">\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0443\u0436\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (initialValue)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#asynchronous_validation\" rel=\"noopener noreferrer nofollow\">\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#asynchronous_validation_and_debounce\" rel=\"noopener noreferrer nofollow\">\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0438 debounce (\u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#dynamic_actions\" rel=\"noopener noreferrer nofollow\">\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0444\u043e\u0440\u043c\u043e\u0439 (\u043e\u0448\u0438\u0431\u043a\u0438, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0441\u0431\u0440\u043e\u0441)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#focus_control\" rel=\"noopener noreferrer nofollow\">\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u043a\u0443\u0441\u043e\u043c<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#reaction_to_change\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#phone_and_country\" rel=\"noopener noreferrer nofollow\">\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043c\u0430\u0441\u043a\u043e\u0439 \u043d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#tests\" rel=\"noopener noreferrer nofollow\">\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u043e\u0440\u043c <\/a><\/p>\n<\/li>\n<\/ul>\n<p><a class=\"anchor\" name=\"custom_fields\" id=\"custom_fields\"><\/a><\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/h2>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u2014 \u043e\u0434\u043d\u0430 \u0438\u0437 \u0441\u0438\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u043e\u0440\u043e\u043d GoForm. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0432\u0438\u0434\u0436\u0435\u0442\u044b, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0449\u0438\u0435 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0441\u0432\u044f\u0437\u044c \u0441 \u0444\u043e\u0440\u043c\u043e\u0439. \u041d\u0438\u0436\u0435 \u2014 \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0438\u043c\u043f\u0443\u0442\u043e\u0432.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 <code>FormFieldModelBase&lt;String&gt;<\/code>, \u0442\u043e <code>FieldController<\/code> \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u043e\u043b\u0435 <code>textController<\/code>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443\u0434\u043e\u0431\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f <code>TextField<\/code> \u0438\u043b\u0438 <code>TextFormField<\/code>. \u042d\u0442\u043e \u0438\u0437\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>TextEditingController<\/code>. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0438\u043f <code>T<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u044b\u043c \u2014 \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e <code>String<\/code>. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>bool<\/code>, <code>int<\/code>, <code>List<\/code>, <code>DateTime<\/code>, \u0438\u043b\u0438 \u0434\u0430\u0436\u0435 \u0432\u0430\u0448\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c. \u042d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 <code>FormFieldModelBase<\/code> \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u043d\u043e\u0432\u043e\u0439 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043b\u044e\u0431\u044b\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u043e\u043b\u0435\u0439.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 1: \u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435<\/h4>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0434\u043b\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044f<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">class GoTextInput extends FormFieldModelBase&lt;String&gt; {   final String label;   final Widget? prefix;   final List&lt;TextInputFormatter&gt;? inputFormatters;   final TextInputType? keyboardType;    GoTextInput({     required super.name,     super.validator,     required this.label,     this.prefix,     this.inputFormatters,     this.keyboardType,     super.key,     super.initialValue,     super.debounceDuration,     super.asyncValidator,   });    @override   Widget build(BuildContext context, FieldController&lt;String&gt; controller) {     return RootInput(       onChanged: (newValue) =&gt; controller.onChange(newValue),       initialValue: controller.value,       validator: validator,       errorText: controller.error,       labelText: label,       prefix: prefix,       inputFormatters: inputFormatters,       focusNode: controller.focusNode,     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 2: \u041f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u043f\u0430\u0440\u043e\u043b\u044f<\/h4>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6d7\/fc5\/29b\/6d7fc529b3f319a6821e1f8a0f538ec8.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6d7\/fc5\/29b\/6d7fc529b3f319a6821e1f8a0f538ec8.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/6d7\/fc5\/29b\/6d7fc529b3f319a6821e1f8a0f538ec8.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:go_form_example\/inputs\/root_input.dart';  class GoPasswordInput extends FormFieldModelBase&lt;String&gt;{   final String label;   GoPasswordInput( {required super.name, super.validator,required this.label,});    @override   Widget build(BuildContext context, FieldController controller) {     return _PasswordField(       controller: controller,       label: label,       validator: validator,     );   } }  class _PasswordField extends StatefulWidget {   final FieldController controller;   final String label;   final String? Function(String?)? validator;    const _PasswordField({     required this.controller,     required this.label,     this.validator,   });    @override   State&lt;_PasswordField&gt; createState() =&gt; _PasswordFieldState(); }  class _PasswordFieldState extends State&lt;_PasswordField&gt; {   bool showPassword = false;    @override   Widget build(BuildContext context) {     return RootInput(       onChanged: (newValue) =&gt; widget.controller.onChange(newValue),       initialValue: widget.controller.value,       validator: widget.validator,       errorText: widget.controller.error,       labelText: widget.label,       suffixIcon: IconButton(         icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),         onPressed: () {           setState(() {             showPassword = !showPassword;           });         },       ),       obscureText: !showPassword,     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 3: CheckBox <\/h4>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a7b\/e06\/263\/a7be06263b17a378e084f71e305c31a4.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a7b\/e06\/263\/a7be06263b17a378e084f71e305c31a4.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a7b\/e06\/263\/a7be06263b17a378e084f71e305c31a4.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u0427\u0435\u043a-\u0431\u043e\u043a\u0441 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430.<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">class GoCheckBox extends FormFieldModelBase&lt;bool&gt; {   final String label;    GoCheckBox({     required super.name,     super.initialValue = false,     super.validator,     required this.label,   });    @override   Widget build(BuildContext context, FieldController controller) {     return Column(       crossAxisAlignment: CrossAxisAlignment.start,       children: [         Row(           children: [             Checkbox(               value: controller.value,               onChanged: (newValue) {                 controller.onChange(newValue);               },             ),             Text(label),           ],         ),         if (controller.error != null)           Padding(             padding: const EdgeInsets.only(top: 4.0),             child: Text(               controller.error!,               style: TextStyle(color: Colors.red),             ),           ),       ],     );  }  }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 4: \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 \u0444\u043e\u0442\u043e<\/h4>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/263\/f3f\/8ed\/263f3f8ed1c2bc133ff251cf1d2c3fce.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/263\/f3f\/8ed\/263f3f8ed1c2bc133ff251cf1d2c3fce.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/263\/f3f\/8ed\/263f3f8ed1c2bc133ff251cf1d2c3fce.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'dart:io'; import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:image_picker\/image_picker.dart';  String formatFileSize(int bytes) {   const suffixes = ['\u0411', '\u041a\u0411', '\u041c\u0411', '\u0413\u0411', '\u0422\u0411'];   double size = bytes.toDouble();   int i = 0;    while (size &gt;= 1024 &amp;&amp; i &lt; suffixes.length - 1) {     size \/= 1024;     i++;   }    return '${size.toStringAsFixed(1)} ${suffixes[i]}'; }  class GoFormFiles extends FormFieldModelBase&lt;List&lt;File&gt;&gt; {   GoFormFiles({     required super.name,     super.initialValue = const [],     super.validator,   });    @override   Widget build(BuildContext context, FieldController&lt;List&lt;File&gt;&gt; controller) {     return Column(       children: [         SizedBox(           width: MediaQuery.of(context).size.width,           child: ElevatedButton(             onPressed: () async {               showModalBottomSheet(                 context: context,                 shape: RoundedRectangleBorder(                   borderRadius: BorderRadius.vertical(top: Radius.circular(30)),                 ),                 builder: (context) {                   return SafeArea(                     child: Wrap(                       children: [                         ListTile(                           leading: Icon(Icons.photo_library),                           title: Text('\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0444\u043e\u0442\u043e'),                           onTap: () async {                             Navigator.of(context).pop();                             final picker = ImagePicker();                             final pickedFile = await picker.pickImage(source: ImageSource.gallery);                             print(pickedFile!=null);                             if (pickedFile != null) {                               final image = File(pickedFile.path);                               controller.onChange([...controller.value ?? [], image]);                             }                           },                         ),                         ListTile(                           leading: Icon(Icons.cancel),                           title: Text('\u041e\u0442\u043c\u0435\u043d\u0430'),                           onTap: () =&gt; Navigator.of(context).pop(),                         ),                       ],                     ),                   );                 },               );             },             child: Text('\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0444\u043e\u0442\u043e'),           ),         ),         ListView.builder(           itemBuilder: (context, index) {             final item = controller.value?[index];             final file = item!;             return ListTile(               contentPadding: EdgeInsets.zero,               leading: Image.file(file),               title: Text(file.path.split('\/').last, maxLines: 2, overflow: TextOverflow.ellipsis),               subtitle: Text(formatFileSize(file.lengthSync())),               trailing: IconButton(                 onPressed: () {                   controller.onChange(List.from(controller.value ?? [])..remove(item));                 },                 icon: const Icon(Icons.delete, color: Colors.red),               ),             );           },           shrinkWrap: true,           itemCount: controller.value?.length ?? 0,         ),         if (controller.error != null)           Text(             controller.error!,             style: const TextStyle(color: Colors.red),           )       ],     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u042d\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 <code>DynamicForm<\/code>, \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u044f \u043f\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f\u0445 UI.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 5: \u0412\u044b\u043f\u0430\u0434\u0430\u044e\u0449\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a (Dropdown)<\/h4>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d83\/049\/8f5\/d830498f5a3c8f981c1d905cde8867c7.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d83\/049\/8f5\/d830498f5a3c8f981c1d905cde8867c7.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d83\/049\/8f5\/d830498f5a3c8f981c1d905cde8867c7.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0432\u0438\u0434\u0436\u0435\u0442\u044b, \u0442\u0430\u043a\u0438\u0435 \u043a\u0430\u043a <code>dropdown_button2<\/code>, <code>flutter_datetime_picker<\/code>, <code>intl_phone_field<\/code> \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u2014 \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0432 \u0438\u0445 \u0432 <code>FormFieldModelBase<\/code> \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u044f \u0441 <code>FieldController<\/code>.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:dropdown_button2\/dropdown_button2.dart'; import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart';  class GoDropdownButton&lt;T&gt; extends FormFieldModelBase&lt;T&gt; {   final List&lt;T&gt; items;    const GoDropdownButton({     required super.name,     required this.items,     super.asyncValidator,     super.initialValue,     super.validator,     super.key,   });    @override   Widget build(BuildContext context, FieldController&lt;T&gt; controller) {     return DropdownButtonHideUnderline(       child: DropdownButton2&lt;T&gt;(         onChanged: (T? value) {           controller.onChange(value);         },         value: controller.value,         items: items             .map(               (item) =&gt; DropdownMenuItem&lt;T&gt;(                 value: item,                 child: Text(item.toString()),               ),             )             .toList(),       ),     );   } }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u043e\u0439 \u0434\u0440\u043e\u043f\u0434\u0430\u0443\u043d \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 <code>DynamicForm<\/code>, \u0437\u0430\u0434\u0430\u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0438, \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438, \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<pre><code class=\"dart\">GoDropdownButton&lt;String&gt;(   name: 'gender',   items: ['\u041c\u0443\u0436\u0441\u043a\u043e\u0439', '\u0416\u0435\u043d\u0441\u043a\u0438\u0439', '\u0414\u0440\u0443\u0433\u043e\u0435'],   validator: (val) {     if (val == null || val.isEmpty) {       return '\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435';     }     return null;   },   initialValue: '\u041c\u0443\u0436\u0441\u043a\u043e\u0439', ),<\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u0438\u043c\u0435\u0440 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u044b<\/h3>\n<p><a class=\"anchor\" name=\"basic_form\" id=\"basic_form\"><\/a><\/p>\n<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u0441\u0430\u043c\u044b\u0445 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u2014 \u0444\u043e\u0440\u043c\u0430 \u043b\u043e\u0433\u0438\u043d\u0430 \u0438\u043b\u0438 \u0432\u0432\u043e\u0434\u0430 \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445. \u041d\u0438\u0436\u0435 \u2014 \u043f\u0440\u0438\u043c\u0435\u0440 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u044b, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0435\u0439 \u0432 \u0441\u0435\u0431\u044f email, \u0442\u0435\u043b\u0435\u0444\u043e\u043d, \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0447\u0435\u043a\u0431\u043e\u043a\u0441 \u0441\u043e\u0433\u043b\u0430\u0441\u0438\u044f:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1dc\/1d8\/26f\/1dc1d826f6136d3ae006f8fd82bcd150.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1dc\/1d8\/26f\/1dc1d826f6136d3ae006f8fd82bcd150.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1dc\/1d8\/26f\/1dc1d826f6136d3ae006f8fd82bcd150.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:go_form_example\/inputs\/go_password_input.dart';  import '..\/inputs\/inputs.dart';  class LoginForm extends StatefulWidget {   const LoginForm({super.key});    @override   State&lt;LoginForm&gt; createState() =&gt; _LoginFormState(); }  class _LoginFormState extends State&lt;LoginForm&gt; {   final _formController = FormController(debug: true);   String result='';    @override   Widget build(BuildContext context) {     return Column(       children: [         DynamicForm(           fields: [             GoTextInput(               name: 'email',               label: 'Email',               validator: (val) {                 if (val == null || val.isEmpty) {                   return '\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c';                 }                 return null;               },             ),             GoPasswordInput(               name: 'password',               label: 'Password',               validator: (val) {                 if (val == null || val.isEmpty) {                   return '\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c';                 }                 return null;               },             ),             GoCheckBox(               name: 'checkbox',               label: 'checkbox',               validator: (val) {                 if (val == null || val == false) {                   return '\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c';                 }                 return null;               },             ),           ],           controller: _formController,         ),         ElevatedButton(           onPressed: () {             _formController.resetAllErrors();             if (!_formController.validate()) {               return;             }             print('${_formController.getValues()}');             setState(() {               result='${_formController.getValues()}';             });           },           child: const Text('\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'),         ),         const SizedBox(           height: 30,         ),         Text(result)       ],     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0430\u043a\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0443 \u0438\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, \u043a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0438\u043c <code>FieldController<\/code>, \u0430 \u0432\u0441\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0435\u0434\u0438\u043d\u044b\u0439 <code>FormController<\/code>. <\/p>\n<h3>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0443\u0436\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (initialValue)<\/h3>\n<p><a class=\"anchor\" name=\"initialValue\" id=\"initialValue\"><\/a><\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0435 \u0441 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u043e\u0439 \u0430\u043d\u043a\u0435\u0442\u044b \u2014 \u0442\u043e \u043f\u043e\u043b\u044f \u0444\u043e\u0440\u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u044b. GoForm \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c <code>initialValue<\/code> \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043f\u043e\u043b\u044e, \u0438 \u043e\u043d\u043e \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e \u0441 <code>FormController<\/code>.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b30\/8bc\/610\/b308bc610d73cfa5d17c96c7ecc2a38b.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b30\/8bc\/610\/b308bc610d73cfa5d17c96c7ecc2a38b.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b30\/8bc\/610\/b308bc610d73cfa5d17c96c7ecc2a38b.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart';  import '..\/inputs\/go_dropdown_button.dart'; import '..\/inputs\/go_dynamic_input.dart';  class InitValuesPage extends StatefulWidget {   const InitValuesPage({super.key});    @override   State&lt;InitValuesPage&gt; createState() =&gt; _InitValuesPageState(); }  class _InitValuesPageState extends State&lt;InitValuesPage&gt; {   final _formController = FormController();    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: Text('\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u044f'),       ),       body: Padding(         padding: const EdgeInsets.all(8.0),         child: Column(           children: [             DynamicForm(               fields: [                 GoDynamicInput(                   name: 'name',                   label: '\u0418\u043c\u044f',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return '\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f';                     }                     return null;                   },                   initialValue: '\u0410\u043b\u0435\u043a\u0441\u0435\u0439',                 ),                 GoDynamicInput(                   name: 'email',                   label: 'Email',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return '\u0412\u0432\u0435\u0434\u0438\u0442\u0435 email';                     }                     final emailRegex = RegExp(r'^[\\w\\.-]+@[\\w\\.-]+\\.\\w+$');                     if (!emailRegex.hasMatch(val)) {                       return '\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 email';                     }                     return null;                   },                   initialValue: 'alexey@example.com',                 ),                 GoDropdownButton(                   name: 'gender',                   items: ['\u041c\u0443\u0436\u0441\u043a\u043e\u0439', '\u0416\u0435\u043d\u0441\u043a\u0438\u0439', '\u0414\u0440\u0443\u0433\u043e\u0435'],                   initialValue: '\u041c\u0443\u0436\u0441\u043a\u043e\u0439',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return '\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b';                     }                     return null;                   },                 ),               ],               controller: _formController,             ),             ElevatedButton(               onPressed: () {                 if (!_formController.validate()) {                   return;                 }                 print('${_formController.getValues()}');               },               child: Text('\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'),             ),            ],         ),       ),     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c <code>initialValue<\/code>. \u042d\u0442\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043f\u0430\u0434\u0443\u0442 \u0432 <code>FormController<\/code> \u0438 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0447\u0435\u0440\u0435\u0437 <code>getValues()<\/code> \u0438\u043b\u0438 <code>getFieldValue(...)<\/code>. \u042d\u0442\u043e \u0443\u0434\u043e\u0431\u043d\u043e \u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u0438 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n<h3>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/h3>\n<p><a class=\"anchor\" name=\"asynchronous_validation\" id=\"asynchronous_validation\"><\/a><\/p>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u0430, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0447\u0435\u0440\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u0438\u0439 API \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.  GoForm \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>asyncValidator<\/code>. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0442\u044c <code>debounceDuration<\/code>, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043b\u0438\u0448\u043d\u0438\u0445 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043f\u0440\u0438 \u0447\u0430\u0441\u0442\u043e\u043c \u0432\u0432\u043e\u0434\u0435 \u0434\u0430\u043d\u043d\u044b\u0445.  \u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0451\u043d \u043f\u0440\u0438\u043c\u0435\u0440 \u0444\u043e\u0440\u043c\u044b \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d9a\/cd3\/167\/d9acd3167f6577abee66f5238b725023.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d9a\/cd3\/167\/d9acd3167f6577abee66f5238b725023.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d9a\/cd3\/167\/d9acd3167f6577abee66f5238b725023.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart';  import '..\/inputs\/go_text_input.dart';  \/\/\/ \u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u0430 \u0441 GoForm. \/\/\/ \u041f\u043e\u0441\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u0432 \u043f\u043e\u043b\u0435 \"Search\" \u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \/\/\/ \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u043e\u0435 \u2014 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u0430. class AsyncValidatorPage extends StatefulWidget {   const AsyncValidatorPage({super.key});    @override   State&lt;AsyncValidatorPage&gt; createState() =&gt; _AsyncValidatorPageState(); }  class _AsyncValidatorPageState extends State&lt;AsyncValidatorPage&gt; {   final formController = FormController();    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: const Text('Async Validator'),       ),       body: Padding(         padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),         child: Column(           children: [             DynamicForm(               fields: [                 GoTextInput(                   name: 'search',                   label: 'Search',                   asyncValidator: (value) async {                     await Future.delayed(const Duration(seconds: 2));                     if (value == null || value.isEmpty) {                       return '\u041f\u043e\u043b\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e';                     }                     \/\/ \u041c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043d\u0430 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u0440\u0435\u0437 API                     if (value == 'admin') {                       return '\u042d\u0442\u043e \u0438\u043c\u044f \u0443\u0436\u0435 \u0437\u0430\u043d\u044f\u0442\u043e';                     }                     return null;                   },                 ),               ],               controller: formController,             ),             const SizedBox(height: 16),             ElevatedButton(               onPressed: () async {                 final result = await formController.validateAsync();                 if (result) {                   final value = formController.getFieldValue&lt;String&gt;('search');                   ScaffoldMessenger.of(context).showSnackBar(                     SnackBar(content: Text('\u0412\u0432\u0435\u0434\u0435\u043d\u043e: $value')),                   );                 }               },               child: const Text('\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c'),             ),           ],         ),       ),     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438:<\/p>\n<ul>\n<li>\n<p><code>asyncValidator<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u0434\u0430\u043d \u043d\u0430 \u043b\u044e\u0431\u043e\u043c \u043f\u043e\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041c\u043e\u0436\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043f\u0440\u0435\u043b\u043e\u0430\u0434\u0435\u0440 \u0440\u044f\u0434\u043e\u043c \u0441 \u043f\u043e\u043b\u0435\u043c \u043f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 (\u0447\u0435\u0440\u0435\u0437 <code>FieldController.status<\/code>).<\/p>\n<\/li>\n<\/ul>\n<h3>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0438 debounce (\u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f)<\/h3>\n<p><a class=\"anchor\" name=\"asynchronous_validation_and_debounce\" id=\"asynchronous_validation_and_debounce\"><\/a><\/p>\n<p>\u0418\u043d\u043e\u0433\u0434\u0430 \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432 \u043f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430, \u0430 \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u0430\u0443\u0437\u044b \u2014 \u044d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f debounce. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0447\u0430\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u043f\u043e\u0438\u0441\u043a\u0435, \u0430\u0432\u0442\u043e\u043a\u043e\u043c\u043f\u043b\u0438\u0442\u0430\u0445 \u0438\u043b\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438.GoForm \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u0434\u0430\u0442\u044c \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 <code>debounceDuration<\/code> \u0438 \u043b\u0435\u0433\u043a\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0443\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e. \u041d\u0438\u0436\u0435 \u2014 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0441 debounce \u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0434\u0438\u043a\u0430\u0446\u0438\u0435\u0439 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u043f\u043e\u043b\u044f: <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/963\/83d\/422\/96383d422d63125b96a60bb18d9d1b49.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/963\/83d\/422\/96383d422d63125b96a60bb18d9d1b49.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/963\/83d\/422\/96383d422d63125b96a60bb18d9d1b49.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:go_form_example\/inputs\/go_text_input.dart';  import '..\/inputs\/search_input.dart';  \/\/\/ Example: Debounced Input Field \/\/\/ \/\/\/ This example demonstrates how to use a debounce delay on a text field \/\/\/ using `GoTextInput` from the `go_form` package. The value change is debounced \/\/\/ by 2 seconds and the result is displayed on the screen. \/\/\/ \/\/\/ This is useful for cases like search inputs, where you want to limit \/\/\/ how often the form reacts to user typing. class DebounceExamplePage extends StatefulWidget {   const DebounceExamplePage({super.key});    @override   State&lt;DebounceExamplePage&gt; createState() =&gt; _DebounceExamplePageState(); }  class _DebounceExamplePageState extends State&lt;DebounceExamplePage&gt; {   final formController = FormController();   String _output = '';    @override   void initState() {     super.initState();     formController.addFieldValueListener((f, v) {       setState(() {         _output = 'Debounced value: $v';       });     });   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: const Text('Debounce'),       ),       body: Padding(         padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),         child: Column(           children: [             DynamicForm(               fields: [                 GoSearchInput(                   name: 'search',                   label: 'Search',                   debounceDuration: const Duration(seconds: 2),                   asyncValidator: (v) async {                     if (v == null || v.isEmpty) {                       return 'Input text';                     }                     await Future.delayed(const Duration(seconds: 2));                     if (v == 'admin') {                       return 'Name exist';                     }                     return null;                   },                   onDebounceComplete: () async {                     print('start onDebounceComplete');                     await formController.validateAsync();                   },                 ),               ],               controller: formController,             ),             const SizedBox(height: 16),             Text(_output),             ElevatedButton(               onPressed: () {                 formController.setError('search', 'error');               },               child: Text('Add error'),             )           ],         ),       ),     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0444\u043e\u0440\u043c\u043e\u0439 (\u043e\u0448\u0438\u0431\u043a\u0438, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0441\u0431\u0440\u043e\u0441)<\/h3>\n<p><a class=\"anchor\" name=\"dynamic_actions\" id=\"dynamic_actions\"><\/a><\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0431\u044b\u0432\u0430\u0435\u0442 \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0438, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u043e\u0442 API (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, &#171;email \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d&#187;). GoForm \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438 \u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u043f\u043e\u043b\u0435\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e:<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<pre><code class=\"dart\">\/\/ \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0448\u0438\u0431\u043a\u0443 \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0435 \u043f\u043e\u043b\u0435: formController.setError('email', '\u0422\u0430\u043a\u043e\u0439 email \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d');  \/\/\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0432\u0440\u0443\u0447\u043d\u0443\u044e \/\/(\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0435\u0434\u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c email \u0438\u0437 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f): formController.setValue('email', 'custom@email.example');  \/\/ \u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439: formController.resetAllFields();  \/\/ \u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432\u0441\u0435 \u043e\u0448\u0438\u0431\u043a\u0438: formController.resetAllErrors(); <\/code><\/pre>\n<p>\u042d\u0442\u0438 \u043c\u0435\u0442\u043e\u0434\u044b \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0433\u0438\u0431\u043a\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u043e\u0440\u043c\u043e\u0439 \u0432 \u043e\u0442\u0432\u0435\u0442 \u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043e\u0442\u0432\u0435\u0442\u044b \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \u043d\u0435 \u043d\u0430\u0440\u0443\u0448\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d0\/6ce\/c6e\/7d06cec6ecfd6116d3f29b448ffb2b2a.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d0\/6ce\/c6e\/7d06cec6ecfd6116d3f29b448ffb2b2a.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d0\/6ce\/c6e\/7d06cec6ecfd6116d3f29b448ffb2b2a.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart';  import '..\/inputs\/go_dynamic_input.dart'; import '..\/inputs\/go_text_input.dart';  class DynamicActionsPage extends StatefulWidget {   const DynamicActionsPage({super.key});    @override   State&lt;DynamicActionsPage&gt; createState() =&gt; _DynamicActionsPageState(); }  class _DynamicActionsPageState extends State&lt;DynamicActionsPage&gt; {   final _formController = FormController();   String _output = '';    @override   void initState() {     super.initState();     _formController.addListener(() {       setState(() {         _output = 'All Values: ${_formController.getValues()}';       });     });     _formController.addFieldValueListener((name, v) {       setState(() {         _output = 'Field \"$name\" changed: $v';       });     });   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: const Text('Dynamic Actions'),       ),       body: Padding(         padding: const EdgeInsets.all(8.0),         child: Column(           children: [             DynamicForm(               fields: [                 GoDynamicInput(                   name: 'text',                   label: 'Email Address',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return 'Please provide an email';                     }                     return null;                   },                 ),               ],               controller: _formController,             ),             TextButton(               onPressed: () {                 _formController.setValue('text', 'custom@email.example');               },               child: const Text('Set Email'),             ),             ElevatedButton(               onPressed: () {                 if (!_formController.validate()) {                   return;                 }                 setState(() {                   _output = 'Validated values: ${_formController.getValues()}';                 });               },               child: const Text('Submit'),             ),             const SizedBox(               height: 30,             ),             ElevatedButton(               onPressed: () {                 _formController.resetAllFields();               },               child: const Text('Reset Fields'),             ),             ElevatedButton(               onPressed: () {                 _formController.setError('text', 'User exist');               },               child: const Text('Set error'),             ),             ElevatedButton(               onPressed: () {                 setState(() {                   _output =                       'Single value: ${_formController.getFieldValue&lt;String&gt;('text')}';                 });               },               child: const Text('Get Field Value'),             ),             const SizedBox(height: 20),             Text(_output),           ],         ),       ),     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u043a\u0443\u0441\u043e\u043c<\/h3>\n<p><a class=\"anchor\" name=\"focus_control\" id=\"focus_control\"><\/a><\/p>\n<p>\u041a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432 GoForm \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0432\u043e\u0438\u043c <code>FieldController<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 <code>FocusNode<\/code>. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0433\u0438\u0431\u043a\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u043e\u043a\u0443\u0441\u043e\u043c \u043a\u0430\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e (\u0447\u0435\u0440\u0435\u0437 <code>formController.focus(...)<\/code>, <code>formController.unfocus(...)<\/code> \u0438 <code>focusNode.requestFocus()<\/code>), \u0442\u0430\u043a \u0438 \u0432\u0440\u0443\u0447\u043d\u0443\u044e. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>focusNode<\/code> \u0432 \u0441\u0432\u043e\u0438\u0445 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0432\u0438\u0434\u0436\u0435\u0442\u0430\u0445 \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u043c \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044f\u043c\u0438, \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0435\u0439, \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u043e\u0439 \u0438 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u043c\u0438.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440: \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u043a\u0443\u0441\u043e\u043c \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u044f\u043c\u0438<\/h4>\n<p>\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u043e\u043a\u0443\u0441\u043e\u043c \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e: \u0441\u0444\u043e\u043a\u0443\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u043c \u043f\u043e\u043b\u0435, \u0441\u043d\u044f\u0442\u044c \u0444\u043e\u043a\u0443\u0441, \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c\u0443 \u043f\u043e\u043b\u044e \u0438 \u0442.\u0434.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f42\/900\/7da\/f429007da89a9f2ea1c3bf96e47632e7.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f42\/900\/7da\/f429007da89a9f2ea1c3bf96e47632e7.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f42\/900\/7da\/f429007da89a9f2ea1c3bf96e47632e7.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart';  import '..\/inputs\/go_password_input.dart'; import '..\/inputs\/go_text_input.dart';  class FocusExamplePage extends StatefulWidget {   const FocusExamplePage({super.key});    @override   State&lt;FocusExamplePage&gt; createState() =&gt; _FocusExamplePageState(); }  class _FocusExamplePageState extends State&lt;FocusExamplePage&gt; {   final form = FormController(debug: true);    @override   void dispose() {     form.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(title: const Text('\u041f\u0440\u0438\u043c\u0435\u0440 \u0444\u043e\u043a\u0443\u0441\u0430')),       body: Padding(         padding: const EdgeInsets.all(16),         child: Column(           children: [             DynamicForm(               fields: [                 GoTextInput(                   label: '\u0418\u043c\u044f',                   name: 'name',                 ),                 GoTextInput(                   label: 'Email',                   name: 'email',                 ),                 GoPasswordInput(                   name: 'password',                   label: '\u041f\u0430\u0440\u043e\u043b\u044c',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return '\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0442\u0435\u0441\u044c';                     }                     return null;                   },                 ),               ],               controller: form,             ),             const SizedBox(height: 32),             Wrap(               spacing: 10,               runSpacing: 10,               children: [                 ElevatedButton(                   onPressed: () =&gt; form.focus('name'),                   child: const Text('\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0438\u043c\u044f'),                 ),                 ElevatedButton(                   onPressed: () =&gt; form.focus('email'),                   child: const Text('\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 email'),                 ),                 ElevatedButton(                   onPressed: () =&gt; form.focus('password'),                   child: const Text('\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u043f\u0430\u0440\u043e\u043b\u044c'),                 ),                 ElevatedButton(                   onPressed: () =&gt; form.unfocus('email'),                   child: const Text('\u0421\u043d\u044f\u0442\u044c \u0444\u043e\u043a\u0443\u0441 \u0441 email'),                 ),                 ElevatedButton(                   onPressed: () =&gt; form.focusNext('name'),                   child: const Text('\u0424\u043e\u043a\u0443\u0441 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u043f\u043e\u0441\u043b\u0435 \"name\"'),                 ),               ],             ),           ],         ),       ),     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u0411\u043e\u043b\u044c\u0448\u0438\u0435 \u0444\u043e\u0440\u043c\u044b \u0438 \u043f\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430 \u043a \u043e\u0448\u0438\u0431\u043a\u0435<\/h4>\n<p>\u042d\u0442\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442, \u043a\u0430\u043a \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443 \u0441 \u0431\u043e\u043b\u044c\u0448\u0438\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c \u043f\u043e\u043b\u0435\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043e\u0448\u0438\u0431\u043a\u0438 \u0444\u043e\u043a\u0443\u0441 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u043e\u043a\u0440\u0443\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043a \u043f\u0435\u0440\u0432\u043e\u043c\u0443 \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u043e\u043c\u0443 \u043f\u043e\u043b\u044e. \u041f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432\u0432\u043e\u0434\u0430 \u0438 UX \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0430\u0445.<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7ca\/75d\/4cc\/7ca75d4cc20672bd88ad73d0d1bd8cb3.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7ca\/75d\/4cc\/7ca75d4cc20672bd88ad73d0d1bd8cb3.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7ca\/75d\/4cc\/7ca75d4cc20672bd88ad73d0d1bd8cb3.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\"> import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import '..\/inputs\/go_text_input.dart';  class BigListPage extends StatefulWidget {   const BigListPage({super.key});    @override   State&lt;BigListPage&gt; createState() =&gt; _BigListPageState(); }  class _BigListPageState extends State&lt;BigListPage&gt; {   final _formController = FormController();   final ScrollController _scrollController = ScrollController();     void _validateForm() {     if(!_formController.validate()){       _formController.scrollToFirstErrorField();       return;     }   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(),       body: Container(         padding: const EdgeInsets.all(24),         child: SingleChildScrollView(           controller: _scrollController,           child: DynamicForm(             fields: [               ...List.generate(50, (index){                 return GoTextInput(                   name: 'text$index',                   label: 'Email',                   validator: (val) {                     if (val == null || val.isEmpty) {                       return 'Fill in the field';                     }                     return null;                   },                   initialValue: 'text ${index}',                 );               }),               GoTextInput(                 name: 'email_error',                 label: 'Email',                 validator: (val) {                   if (val == null || val.isEmpty) {                     return 'Fill in the field';                   }                   return null;                 },               )             ],             controller: _formController,           ),         ),       ),       bottomNavigationBar: SafeArea(         child: Container(           padding: const EdgeInsets.symmetric(horizontal: 10),           child: ElevatedButton(             onPressed: _validateForm,             child: const Text('Validate'),           ),         ),       ),     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>\u0420\u0435\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f<\/h2>\n<p><a class=\"anchor\" name=\"reaction_to_change\" id=\"reaction_to_change\"><\/a><\/p>\n<p>\u0412\u043e \u043c\u043d\u043e\u0433\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0432\u0430\u0436\u043d\u043e \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u0444\u043e\u0440\u043c\u044b \u2014 \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0438, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c UI \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043f\u043e\u043b\u0435\u0439. GoForm \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e  \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e:<\/p>\n<ul>\n<li>\n<p><code>addFieldValueListener<\/code> \u2014 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0432 \u043f\u043e\u043b\u044f\u0445.<\/p>\n<\/li>\n<li>\n<p><code>addValidationListener<\/code> \u2014 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u0442, \u043a\u043e\u0433\u0434\u0430 \u0444\u043e\u0440\u043c\u0430 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439 \u0438\u043b\u0438 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0439.<\/p>\n<\/li>\n<li>\n<p><code>addFocusListener<\/code> \u2014 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u0441\u043b\u0435\u0434\u0438\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0435 \u043f\u043e\u043b\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e \u0438\u043b\u0438 \u043f\u043e\u0442\u0435\u0440\u044f\u043b\u043e \u0444\u043e\u043a\u0443\u0441.<\/p>\n<\/li>\n<\/ul>\n<p>\u042d\u0442\u0438 \u0441\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u0438 \u0443\u043f\u0440\u043e\u0449\u0430\u044e\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432 \u0438 \u0431\u0438\u0437\u043d\u0435\u0441-\u043b\u043e\u0433\u0438\u043a\u0438, \u0440\u0435\u0430\u0433\u0438\u0440\u0443\u044e\u0449\u0435\u0439 \u043d\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440: \u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439<\/h4>\n<p>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442, \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>addFieldValueListener<\/code> \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b. \u041a\u0430\u0436\u0434\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0447\u0430\u0442\u0430\u0442\u044c\u0441\u044f \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435\u043c \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044f \u0438 \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f.<\/p>\n<pre><code class=\"dart\">_formController.addFieldValueListener((name, value) {       print('\u041f\u043e\u043b\u0435 \"$name\" \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e: $value');     });<\/code><\/pre>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440: \u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c \u0444\u043e\u043a\u0443\u0441\u0430<\/h4>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>addFocusListener<\/code> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u043f\u043e\u0442\u0435\u0440\u044e \u0444\u043e\u043a\u0443\u0441\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a, \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<pre><code class=\"dart\">_formController.addFocusListener((name, focus) {     print('\u041f\u043e\u043b\u0435 $name ${focus.hasFocus ? \"\u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\" : \"\u043f\u043e\u0442\u0435\u0440\u044f\u043b\u043e\"} \u0444\u043e\u043a\u0443\u0441');   });<\/code><\/pre>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440: \u0421\u043b\u0443\u0448\u0430\u0442\u0435\u043b\u044c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438<\/h4>\n<pre><code class=\"dart\">_formController.addValidationListener((v){           print('\u0424\u043e\u0440\u043c\u0430 ${v ? \"\u043f\u0440\u043e\u0448\u043b\u0430\" : \"\u043d\u0435 \u043f\u0440\u043e\u0448\u043b\u0430\"} \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e');      });<\/code><\/pre>\n<h2>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043c\u0430\u0441\u043a\u043e\u0439 \u043d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430<\/h2>\n<p><a class=\"anchor\" name=\"phone_and_country\" id=\"phone_and_country\"><\/a><\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0441 \u0432\u044b\u0431\u043e\u0440\u043e\u043c \u0441\u0442\u0440\u0430\u043d\u044b \u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u043c\u0430\u0441\u043a\u0438 <a href=\"https:\/\/pub.dev\/packages\/mask_text_input_formatter\" rel=\"noopener noreferrer nofollow\">mask_text_input_formatter<\/a>.<\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u043d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u044b\u0431\u043e\u0440\u0430 \u043a\u043e\u0434\u0430 \u0441\u0442\u0440\u0430\u043d\u044b \u0438 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043c\u0430\u0441\u043a\u043e\u0439. \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>mask_text_input_formatter<\/code> \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0443\u0436\u043d\u043e\u0439 \u043c\u0430\u0441\u043a\u0438, \u0437\u0430\u0432\u0438\u0441\u044f\u0449\u0435\u0439 \u043e\u0442 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u044b. <\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/671\/e12\/7e8\/671e127e85643bc8b0fbe9ee025a90f3.gif\" width=\"296\" height=\"640\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/671\/e12\/7e8\/671e127e85643bc8b0fbe9ee025a90f3.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/671\/e12\/7e8\/671e127e85643bc8b0fbe9ee025a90f3.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:mask_text_input_formatter\/mask_text_input_formatter.dart'; import 'package:flutter_libphonenumber\/flutter_libphonenumber.dart';  import '..\/domain\/country_phone_dto.dart'; import '..\/inputs\/go_text_input.dart'; import '..\/inputs\/root_input.dart';  class PhoneAndCountry extends StatefulWidget {   const PhoneAndCountry({super.key});    @override   State&lt;PhoneAndCountry&gt; createState() =&gt; _PhoneAndCountryState(); }  class _PhoneAndCountryState extends State&lt;PhoneAndCountry&gt; {   final formController = FormController(debug: true);    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(),       body: Padding(         padding: const EdgeInsets.all(8.0),         child: Column(           children: [             DynamicForm(fields: [               GoPhoneAndCountryInput(name: 'phoneAndCountrty'),             ], controller: formController),             const SizedBox(height: 10),             ElevatedButton(               onPressed: () {                 debugPrint(formController.getFieldValue&lt;String&gt;('phone'));               },               child: const Text('\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f'),             ),           ],         ),       ),     );   } }  class PhoneState {   final String? value;   final MaskTextInputFormatter maskFormatter;   final CountryPhoneDto country;    const PhoneState({     this.value,     required this.maskFormatter,     required this.country,   });    PhoneState copyWith({     String? value,     MaskTextInputFormatter? maskFormatter,     CountryPhoneDto? country,     bool? clearPhone,   }) =&gt;       PhoneState(         value: (clearPhone == true) ? null : value ?? this.value,         maskFormatter: maskFormatter ?? this.maskFormatter,         country: country ?? this.country,       );    @override   bool operator ==(Object other) =&gt;       identical(this, other) ||       other is PhoneState &amp;&amp;           runtimeType == other.runtimeType &amp;&amp;           value == other.value &amp;&amp;           maskFormatter == other.maskFormatter &amp;&amp;           country == other.country;    @override   int get hashCode =&gt; Object.hash(value, maskFormatter, country); }  List&lt;CountryPhoneDto&gt; countries = [   CountryPhoneDto(     name: '\u0420\u043e\u0441\u0441\u0438\u044f',     nativeName: '\u0420\u043e\u0441\u0441\u0438\u044f',     countryCode: 'RU',     dialCode: '+7',     flagEmoji: '\ud83c\uddf7\ud83c\uddfa',     phoneMask: '(###) ###-##-##',     priority: 1,   ),   CountryPhoneDto(     name: '\u0421\u0428\u0410',     nativeName: 'United States',     countryCode: 'US',     dialCode: '+1',     flagEmoji: '\ud83c\uddfa\ud83c\uddf8',     phoneMask: '(###) ###-####',     priority: 2,   ),   CountryPhoneDto(     name: '\u0413\u0435\u0440\u043c\u0430\u043d\u0438\u044f',     nativeName: 'Deutschland',     countryCode: 'DE',     dialCode: '+49',     flagEmoji: '\ud83c\udde9\ud83c\uddea',     phoneMask: '#### ########',     priority: 4,   ),   CountryPhoneDto(     name: '\u0424\u0440\u0430\u043d\u0446\u0438\u044f',     nativeName: 'France',     countryCode: 'FR',     dialCode: '+33',     flagEmoji: '\ud83c\uddeb\ud83c\uddf7',     phoneMask: '# ## ## ## ##',     priority: 5,   ),   CountryPhoneDto(     name: '\u0411\u0440\u0430\u0437\u0438\u043b\u0438\u044f',     nativeName: 'Brasil',     countryCode: 'BR',     dialCode: '+55',     flagEmoji: '\ud83c\udde7\ud83c\uddf7',     phoneMask: '(##) #####-####',     priority: 6,   ), ];  class GoPhoneAndCountryInput extends FormFieldModelBase&lt;PhoneState&gt; {   final String? label;    const GoPhoneAndCountryInput({     required super.name,     this.label,   });    @override   void onInit(FieldController&lt;PhoneState&gt; controller) {     final country = countries.firstWhere(       (c) =&gt; c.countryCode == 'RU',       orElse: () =&gt; countries.first,     );     final maskFormatter = MaskTextInputFormatter(       mask: country.phoneMask,       filter: {\"#\": RegExp(r'[0-9]')},     );     controller.setValue(       PhoneState(value: '', maskFormatter: maskFormatter, country: country),     );          super.onInit(controller);   }    @override   Widget build(BuildContext context, FieldController&lt;PhoneState&gt; controller) {     final value = controller.value;     if (value == null) {       return Container();     }      return RootInput(       initialValue: controller.value?.value,       key: ValueKey(controller.value!.maskFormatter.getMask()),       onChanged: (newValue) =&gt;           controller.onChange(controller.value?.copyWith(value: newValue)),       errorText: controller.error,       labelText: label,       prefix: InkWell(         onTap: () =&gt; _showCountrySelectionSheet(context, controller),         child: Text(           '${controller.value?.country.flagEmoji} ${controller.value?.country.dialCode} ',         ),       ),       inputFormatters: [controller.value!.maskFormatter],       focusNode: controller.focusNode,     );   }    void _showCountrySelectionSheet(       BuildContext context, FieldController&lt;PhoneState&gt; controller) {     showModalBottomSheet(       context: context,       shape: const RoundedRectangleBorder(         borderRadius: BorderRadius.vertical(top: Radius.circular(16)),       ),       builder: (context) {         return Padding(           padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),           child: Column(             mainAxisSize: MainAxisSize.min,             children: [               Text(                 '\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0443',                 style: Theme.of(context).textTheme.titleMedium,               ),               const SizedBox(height: 16),               Expanded(                 child: ListView.builder(                   itemCount: countries.length,                   itemBuilder: (context, index) {                     final country = countries[index];                     return Padding(                       padding: const EdgeInsets.symmetric(vertical: 4),                       child: InkWell(                         onTap: () {                           controller.setValue(                             controller.value?.copyWith(                               country: country,                               maskFormatter: MaskTextInputFormatter(                                 mask: country.phoneMask,                                 filter: {\"#\": RegExp(r'[0-9]')},                               ),                               clearPhone: true,                             ),                           );                           controller.value?.maskFormatter.clear();                           Navigator.of(context).pop();                         },                         child: Row(                           mainAxisAlignment: MainAxisAlignment.spaceBetween,                           children: [                             Text('${country.flagEmoji} ${country.name}'),                             Text(country.dialCode),                           ],                         ),                       ),                     );                   },                 ),               ),             ],           ),         );       },       isScrollControlled: true,     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u043e\u0440\u043c<\/h2>\n<p><a class=\"anchor\" name=\"tests\" id=\"tests\"><\/a><\/p>\n<pre><code class=\"dart\">import 'package:flutter_test\/flutter_test.dart'; import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:go_form_example\/pages\/test_form_page.dart';  void main() {   group('TestFormPage Tests', () {     testWidgets('TestFormPage renders correctly', (WidgetTester tester) async {       await tester.pumpWidget(MaterialApp(home: TestFormPage()));        expect(find.text('Test form'), findsOneWidget);       expect(find.byType(DynamicForm), findsOneWidget);       expect(find.byType(TextFormField), findsOneWidget);     });      testWidgets('Entering text updates the field value', (WidgetTester tester) async {       await tester.pumpWidget(MaterialApp(home: TestFormPage()));       final textField = find.byType(TextFormField);       await tester.enterText(textField, 'new@example.com');       await tester.pump();       expect(find.text('new@example.com'), findsOneWidget);     });      testWidgets('Validation error appears and disappears correctly', (WidgetTester tester) async {       await tester.pumpWidget(MaterialApp(home: TestFormPage()));       final textField = find.byType(TextFormField);       await tester.enterText(textField, '');       final validateButton = find.byKey(const Key('validate_button'));       await tester.tap(validateButton);       await tester.pump();       expect(find.text('\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c'), findsOneWidget);        await tester.enterText(textField, 'valid@example.com');       await tester.pump();       expect(find.text('\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c'), findsNothing);     });      testWidgets('Reset button clears validation errors', (WidgetTester tester) async {       await tester.pumpWidget(MaterialApp(home: TestFormPage()));       final textField = find.byType(TextFormField);       await tester.enterText(textField, '');       await tester.pump();       final validateButton = find.byKey(Key('validate_button'));       await tester.tap(validateButton);       await tester.pump();       expect(find.text('\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c'), findsOneWidget);        final resetButton = find.byKey(Key('reset_button'));       await tester.tap(resetButton);       await tester.pump();       expect(find.text('\u0421\u043e\u0433\u043b\u0430\u0441\u0438\u0441\u044c'), findsNothing);     });      testWidgets('Manual error set using setError() is displayed', (WidgetTester tester) async {       await tester.pumpWidget(MaterialApp(home: TestFormPage()));       final formWidget = tester.widget&lt;DynamicForm&gt;(find.byType(DynamicForm));       final formController = formWidget.controller;       formController.setError('text', '\u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430');       await tester.pump();       expect(find.text('\u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430'), findsOneWidget);     });      testWidgets('Form does not rebuild unnecessarily', (WidgetTester tester) async {       int buildCount = 0;       await tester.pumpWidget(         StatefulBuilder(           builder: (context, setState) {             buildCount++;             return MaterialApp(home: TestFormPage());           },         ),       );       final textField = find.byType(TextFormField);       await tester.enterText(textField, 'test@example.com');       await tester.pump();       expect(buildCount, lessThan(3));     });   }); }<\/code><\/pre>\n<p>\u042d\u0442\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>flutter_test<\/code> \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043b\u043e\u0433\u0438\u043a\u0438 \u0444\u043e\u0440\u043c, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0441\u0431\u0440\u043e\u0441 \u0438 \u0440\u0443\u0447\u043d\u0430\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043b\u044e\u0447\u0438 (<code>Key<\/code>) \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043e\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0449\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u043d\u0438\u043c\u0438 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445.<\/p>\n<h2>\u0427\u0442\u043e \u043c\u044b \u0443\u0437\u043d\u0430\u043b\u0438 \u043e GoForm:<\/h2>\n<ul>\n<li>\n<p>\u0417\u0430\u0431\u0438\u043b\u0438 \u0433\u0432\u043e\u0437\u0434\u044c \u0432 \u043a\u0440\u044b\u0448\u043a\u0443 \u0433\u0440\u043e\u0431\u0430 Form<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0443\u0447\u0438\u043b\u0438\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u0431\u0435\u0437 \u0433\u043e\u043b\u043e\u0432\u043d\u043e\u0439 \u0431\u043e\u043b\u0438<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u043b\u0438\u0441\u044c \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u0448\u043c\u0430\u0440\u043e\u0432 <\/p>\n<\/li>\n<li>\n<p>\u0423\u0437\u043d\u0430\u043b\u0438, \u043a\u0430\u043a \u043f\u043e\u0434\u0440\u0443\u0436\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u044b \u0441 \u043b\u044e\u0431\u044b\u043c state management <\/p>\n<\/li>\n<\/ul>\n<p><strong>\u0413\u043b\u0430\u0432\u043d\u044b\u0435 \u043f\u043b\u044e\u0441\u044b GoForm, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b \u0443\u0436\u0435 \u043e\u0446\u0435\u043d\u0438\u043b\u0438:<\/strong><\/p>\n<ul>\n<li>\n<p>\u0415\u0434\u0438\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0434\u043b\u044f \u0432\u0441\u0435\u0439 \u0444\u043e\u0440\u043c\u044b \u2014 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043e\u0432 <\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u2014 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043f\u0440\u043e \u0440\u0443\u0442\u0438\u043d\u0443 <\/p>\n<\/li>\n<li>\n<p>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u043c\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043c\u0438 \u2014 \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0447\u0430\u0441\u044b <\/p>\n<\/li>\n<li>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u2014 \u043a\u043e\u0434 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0434\u044b\u0445\u0430\u043d\u0438\u0438 <\/p>\n<\/li>\n<\/ul>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u0447\u0435\u0441\u0442\u043d\u043e: \u0434\u0430\u0436\u0435 \u0441\u0430\u043c\u0430\u044f \u043a\u0440\u0443\u0442\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u0438\u0442 \u0432\u0430\u0448\u0435\u0433\u043e \u043e\u043f\u044b\u0442\u0430. GoForm \u2014 \u044d\u0442\u043e \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u043a\u043e\u0434\u0430 \u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u0441\u043f\u0430\u0442\u044c \u043f\u043e \u043d\u043e\u0447\u0430\u043c<\/p>\n<p>\u0427\u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435? \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0447\u0442\u043e \u0437\u0430 reactive_forms \u0438 \u0437\u0430\u0447\u0435\u043c \u043e\u043d \u043d\u0443\u0436\u0435\u043d.<\/p>\n<p><strong>P.S.<\/strong> \u0415\u0441\u043b\u0438 \u0432\u044b \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u0434\u043e \u043a\u043e\u043d\u0446\u0430 \u2014 \u0432\u044b \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 \u0433\u0435\u0440\u043e\u0439! <\/p>\n<p>\u041d\u0430\u0448\u043b\u0438 \u0431\u0430\u0433, \u0435\u0441\u0442\u044c \u0438\u0434\u0435\u0438 \u0438\u043b\u0438 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u00ab\u0441\u043f\u0430\u0441\u0438\u0431\u043e\u00bb \u2014 \u043f\u0438\u0448\u0438\u0442\u0435, \u043d\u0435 \u0441\u0442\u0435\u0441\u043d\u044f\u0439\u0442\u0435\u0441\u044c.<\/p>\n<p>\u0410 \u0435\u0449\u0451 \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u043f\u043e Flutter \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u2014 \u0432 \u043c\u043e\u0451\u043c<a href=\"https:\/\/t.me\/kotelnikoff_dev\" rel=\"noopener noreferrer nofollow\"> Telegram-\u043a\u0430\u043d\u0430\u043b\u0435<\/a> \u2014 \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0439\u0442\u0435\u0441\u044c, \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e!<\/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\/925282\/\"> https:\/\/habr.com\/ru\/articles\/925282\/<\/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<p>\u0420\u0430\u0434 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0430\u043c \u0441\u0432\u043e\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443\u00a0<a href=\"https:\/\/pub.dev\/packages\/go_form\" rel=\"noopener noreferrer nofollow\"><strong>GoForm<\/strong><\/a>\u00a0\u2014 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b\u0440\u043e\u0441\u043b\u043e \u0438\u0437 \u0431\u043e\u043b\u0438 \u0438 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439 \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 Flutter.<\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/922846\/\" rel=\"noopener noreferrer nofollow\">\u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438, \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b Flutter \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (Form, TextFormField, GlobalKey) \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442 \u0442\u0440\u0435\u0449\u0430\u0442\u044c \u043f\u043e \u0448\u0432\u0430\u043c \u043f\u0440\u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041f\u043e\u043c\u043d\u0438\u0442\u0435 \u044d\u0442\u0438 \u0431\u0435\u0441\u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0435 TextEditingController, \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439 \u0438 \u0442\u0430\u043d\u0446\u044b \u0441 \u0431\u0443\u0431\u043d\u043e\u043c \u0432\u043e\u043a\u0440\u0443\u0433 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f?<\/p>\n<p>\u042f \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u044d\u0442\u0438\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u043d\u0435 \u0440\u0430\u0437 \u0438 \u043d\u0435 \u0434\u0432\u0430, \u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0430\u0442\u044c \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434, \u0440\u0435\u0448\u0438\u043b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0437\u0431\u0430\u0432\u0438\u0442 \u0432\u0430\u0441 \u043e\u0442 \u044d\u0442\u0438\u0445 \u0441\u0442\u0440\u0430\u0434\u0430\u043d\u0438\u0439. \u0422\u0430\u043a \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f <a href=\"https:\/\/pub.dev\/packages\/go_form\" rel=\"noopener noreferrer nofollow\">GoForm<\/a> <\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c, \u043a\u0430\u043a GoForm \u0440\u0435\u0448\u0430\u0435\u0442 \u0432\u0441\u0435 \u0442\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438, \u0438 \u0434\u0430\u0436\u0435 \u0431\u043e\u043b\u044c\u0448\u0435.<\/p>\n<p>\u0413\u043e\u0442\u043e\u0432\u044b \u0443\u0437\u043d\u0430\u0442\u044c, \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 \u0432\u043e Flutter \u043f\u0440\u0438\u044f\u0442\u043d\u043e\u0439 \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u043e\u0439? \u0422\u043e\u0433\u0434\u0430 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0447\u043d\u0435\u043c!<\/p>\n<p>\u0418 \u0434\u0430, \u044d\u0442\u043e \u043c\u043e\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u2014 \u044f \u0437\u043d\u0430\u044e \u043e \u0435\u0451 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u0430\u043c\u043d\u044f\u0445 \u043d\u0435 \u043f\u043e\u043d\u0430\u0441\u043b\u044b\u0448\u043a\u0435 \u0438 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0435\u043c\u0438 \u0442\u043e\u043d\u043a\u043e\u0441\u0442\u044f\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f. \u0418 \u043a\u043e\u0441\u0442\u044b\u043b\u044f\u043c\u0438. <\/p>\n<h2>\u041f\u043e\u0447\u0435\u043c\u0443 GoForm?<\/h2>\n<p>GoForm \u2014 \u044d\u0442\u043e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u043e\u0435:<\/p>\n<ul>\n<li>\n<p>\u0423\u043f\u0440\u043e\u0449\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0440\u0443\u0442\u0438\u043d\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 (\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f, \u0444\u043e\u043a\u0443\u0441, \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435)<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u044b\u0439 API \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 (\u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0439)<\/p>\n<\/li>\n<li>\n<p>\u041b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441 Riverpod, Bloc, Provider \u0438 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u043c\u0438<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u0434\u043d\u043e\u0433\u043e <code>FormController<\/code><\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u0430\u043a\u0438\u0435 \u0444\u0438\u0447\u0438 \u043a\u0430\u043a <code>debounce<\/code>, \u0441\u043a\u0440\u043e\u043b\u043b \u043a \u043e\u0448\u0438\u0431\u043a\u0430\u043c, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 Flutter:<\/strong><\/p>\n<ul>\n<li>\n<p>\u0415\u0434\u0438\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<\/li>\n<li>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u0438\u0441\u0430\u0442\u044c <code>TextEditingController<\/code> \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u043c\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0413\u0438\u0431\u043a\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439.<\/p>\n<\/li>\n<li>\n<p>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u0442\u0438\u0447\u043d\u044b\u0439 \u0438 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<\/li>\n<\/ul>\n<p>\u26a0\ufe0f\u00a0<strong>\u0412\u0430\u0436\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435:<\/strong>\u00a0\u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0431\u0443\u0434\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430, \u0438 \u044d\u0442\u043e \u043a\u0440\u0443\u0442\u043e! \u041d\u043e \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c \u0447\u0442\u0435\u043d\u0438\u0435 \u0432 \u043c\u0430\u0440\u0430\u0444\u043e\u043d \u043f\u043e \u043f\u0443\u0441\u0442\u044b\u043d\u0435, \u044f \u0441\u043f\u0440\u044f\u0442\u0430\u043b \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u043f\u043e\u0434 \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u044b.<\/p>\n<h2>\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435:<\/h2>\n<ul>\n<li>\n<p><a href=\"#custom_fields\" rel=\"noopener noreferrer nofollow\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#basic_form\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u044b<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#initialValue\" rel=\"noopener noreferrer nofollow\">\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0443\u0436\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (initialValue)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#asynchronous_validation\" rel=\"noopener noreferrer nofollow\">\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#asynchronous_validation_and_debounce\" rel=\"noopener noreferrer nofollow\">\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0438 debounce (\u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#dynamic_actions\" rel=\"noopener noreferrer nofollow\">\u0414\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0444\u043e\u0440\u043c\u043e\u0439 (\u043e\u0448\u0438\u0431\u043a\u0438, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0441\u0431\u0440\u043e\u0441)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#focus_control\" rel=\"noopener noreferrer nofollow\">\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0444\u043e\u043a\u0443\u0441\u043e\u043c<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#reaction_to_change\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u0430\u043a\u0446\u0438\u044f \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#phone_and_country\" rel=\"noopener noreferrer nofollow\">\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u043c\u0430\u0441\u043a\u043e\u0439 \u043d\u043e\u043c\u0435\u0440\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#tests\" rel=\"noopener noreferrer nofollow\">\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u043e\u0440\u043c <\/a><\/p>\n<\/li>\n<\/ul>\n<p><a class=\"anchor\" name=\"custom_fields\" id=\"custom_fields\"><\/a><\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439<\/h2>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u2014 \u043e\u0434\u043d\u0430 \u0438\u0437 \u0441\u0438\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u043e\u0440\u043e\u043d GoForm. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043b\u044e\u0431\u044b\u0435 \u0432\u0438\u0434\u0436\u0435\u0442\u044b, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0449\u0438\u0435 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u0441\u0432\u044f\u0437\u044c \u0441 \u0444\u043e\u0440\u043c\u043e\u0439. \u041d\u0438\u0436\u0435 \u2014 \u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0438\u043c\u043f\u0443\u0442\u043e\u0432.<\/p>\n<p>\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 <code>FormFieldModelBase&lt;String&gt;<\/code>, \u0442\u043e <code>FieldController<\/code> \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u043e\u043b\u0435 <code>textController<\/code>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0443\u0434\u043e\u0431\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f <code>TextField<\/code> \u0438\u043b\u0438 <code>TextFormField<\/code>. \u042d\u0442\u043e \u0438\u0437\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>TextEditingController<\/code>. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0438\u043f <code>T<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u044b\u043c \u2014 \u043d\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e <code>String<\/code>. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>bool<\/code>, <code>int<\/code>, <code>List<\/code>, <code>DateTime<\/code>, \u0438\u043b\u0438 \u0434\u0430\u0436\u0435 \u0432\u0430\u0448\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c. \u042d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 <code>FormFieldModelBase<\/code> \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u043d\u043e\u0432\u043e\u0439 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043b\u044e\u0431\u044b\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0435\u043c\u044b\u0445 \u043f\u043e\u043b\u0435\u0439.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 1: \u0422\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435<\/h4>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u0434\u043b\u044f \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044f<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">class GoTextInput extends FormFieldModelBase&lt;String&gt; {   final String label;   final Widget? prefix;   final List&lt;TextInputFormatter&gt;? inputFormatters;   final TextInputType? keyboardType;    GoTextInput({     required super.name,     super.validator,     required this.label,     this.prefix,     this.inputFormatters,     this.keyboardType,     super.key,     super.initialValue,     super.debounceDuration,     super.asyncValidator,   });    @override   Widget build(BuildContext context, FieldController&lt;String&gt; controller) {     return RootInput(       onChanged: (newValue) =&gt; controller.onChange(newValue),       initialValue: controller.value,       validator: validator,       errorText: controller.error,       labelText: label,       prefix: prefix,       inputFormatters: inputFormatters,       focusNode: controller.focusNode,     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 2: \u041f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430 \u043f\u0430\u0440\u043e\u043b\u044f<\/h4>\n<figure class=\"\"><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:go_form_example\/inputs\/root_input.dart';  class GoPasswordInput extends FormFieldModelBase&lt;String&gt;{   final String label;   GoPasswordInput( {required super.name, super.validator,required this.label,});    @override   Widget build(BuildContext context, FieldController controller) {     return _PasswordField(       controller: controller,       label: label,       validator: validator,     );   } }  class _PasswordField extends StatefulWidget {   final FieldController controller;   final String label;   final String? Function(String?)? validator;    const _PasswordField({     required this.controller,     required this.label,     this.validator,   });    @override   State&lt;_PasswordField&gt; createState() =&gt; _PasswordFieldState(); }  class _PasswordFieldState extends State&lt;_PasswordField&gt; {   bool showPassword = false;    @override   Widget build(BuildContext context) {     return RootInput(       onChanged: (newValue) =&gt; widget.controller.onChange(newValue),       initialValue: widget.controller.value,       validator: widget.validator,       errorText: widget.controller.error,       labelText: widget.label,       suffixIcon: IconButton(         icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),         onPressed: () {           setState(() {             showPassword = !showPassword;           });         },       ),       obscureText: !showPassword,     );   } } <\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 3: CheckBox <\/h4>\n<figure class=\"\"><\/figure>\n<details class=\"spoiler\">\n<summary>\u0427\u0435\u043a-\u0431\u043e\u043a\u0441 \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u0430.<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">class GoCheckBox extends FormFieldModelBase&lt;bool&gt; {   final String label;    GoCheckBox({     required super.name,     super.initialValue = false,     super.validator,     required this.label,   });    @override   Widget build(BuildContext context, FieldController controller) {     return Column(       crossAxisAlignment: CrossAxisAlignment.start,       children: [         Row(           children: [             Checkbox(               value: controller.value,               onChanged: (newValue) {                 controller.onChange(newValue);               },             ),             Text(label),           ],         ),         if (controller.error != null)           Padding(             padding: const EdgeInsets.only(top: 4.0),             child: Text(               controller.error!,               style: TextStyle(color: Colors.red),             ),           ),       ],     );  }  }<\/code><\/pre>\n<\/div>\n<\/details>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 4: \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 \u0444\u043e\u0442\u043e<\/h4>\n<figure class=\"\"><\/figure>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434 \u043f\u0440\u0438\u043c\u0435\u0440\u0430<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"dart\">import 'dart:io'; import 'package:flutter\/material.dart'; import 'package:go_form\/go_form.dart'; import 'package:image_picker\/image_picker.dart';  String formatFileSize(int bytes) {   const suffixes = ['\u0411', '\u041a\u0411', '\u041c\u0411', '\u0413\u0411', '\u0422\u0411'];   double size = bytes.toDouble();   int i = 0;    while (size &gt;= 1024 &amp;&amp; i &lt; suffixes.length - 1) {     size \/= 1024;     i++;   }    return '${size.toStringAsFixed(1)} ${suffixes[i]}'; }  class GoFormFiles extends FormFieldModelBase&lt;List&lt;File&gt;&gt; {   GoFormFiles({     required super.name,     super.initialValue = const [],     super.validator,   });    @override   Widget build(BuildContext context, FieldController&lt;List&lt;File&gt;&gt; controller) {     return Column(       children: [         SizedBox(           width: MediaQuery.of(context).size.width,           child: ElevatedButton(             onPressed: () async {               showModalBottomSheet(                 context: context,                 shape: RoundedRectangleBorder(                   borderRadius: BorderRadius.vertical(top: Radius.circular(30)),                 ),                 builder: (context) {                   return SafeArea(                     child: Wrap(                       children: [                         ListTile(                           leading: Icon(Icons.photo_library),                           title: Text('\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0444\u043e\u0442\u043e'),                           onTap: () async {                             Navigator.of(context).pop();                             final picker = ImagePicker();                             final pickedFile = await picker.pickImage(source: ImageSource.gallery);                             print(pickedFile!=null);                             if (pickedFile != null) {                               final image = File(pickedFile.path);                               controller.onChange([...controller.value ?? [], image]);                             }                           },                         ),                         ListTile(                           leading: Icon(Icons.cancel),                           title: Text('\u041e\u0442\u043c\u0435\u043d\u0430'),                           onTap: () =&gt; Navigator.of(context).pop(),                         ),                       ],                     ),                   );                 },               );             },             child: Text('\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0444\u043e\u0442\u043e'),           ),         ),         ListView.builder(           itemBuilder: (context, index) {             final item = controller.value?[index];             final file = item!;             return ListTile(               contentPadding: EdgeInsets.zero,               leading: Image.file(file),               title: Text(file.path.split('\/').last, maxLines: 2, overflow: TextOverflow.ellipsis),               subtitle: Text(formatFileSize(file.lengthSync())),               trailing: IconButton(                 onPressed: () {                   controller.onChange(List.from(controller.value ?? [])..remove(item));                 },                 icon: const Icon(Icons.delete, color: Colors.red),               ),             );           },           shrinkWrap: true,           itemCount: controller.value?.length ?? 0,         ),         if (controller.error != null)           Text(             controller.error!,             style: const TextStyle(color: Colors.red),           )       ],     );   } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u042d\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 <code>DynamicForm<\/code>, \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u0443\u044f \u043f\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f\u0445 UI.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 5: \u0412\u044b\u043f\u0430\u0434\u0430\u044e\u0449\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a (Dropdown)<\/h4>\n<figure class=\"\"><\/figure>\n<p>\u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0432\u0438\u0434\u0436\u0435\u0442\u044b, \u0442\u0430\u043a\u0438\u0435 \u043a\u0430\u043a <code>dropdown_button2<\/code>, <code>flutter_d<\/code><\/p>\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-466292","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/466292","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=466292"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/466292\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=466292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=466292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=466292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}