From 260815a056fd7ec8b29ea2734327b1b311d104af Mon Sep 17 00:00:00 2001 From: facebook-github-bot Date: Fri, 26 Jul 2024 16:36:42 +0000 Subject: [PATCH] [ci skip] Deploying documentation update --- 404.html | 4 ++-- _src/getting-started/android-native.mdx | 8 ++++---- _src/getting-started/react-native-ios.mdx | 2 +- _src/getting-started/react-native.mdx | 4 ++-- assets/js/{0fc226a0.61b1ee64.js => 0fc226a0.1a542511.js} | 2 +- assets/js/{303bd344.e771e401.js => 303bd344.9a474805.js} | 2 +- assets/js/{3a952f51.620f906d.js => 3a952f51.c7de4763.js} | 2 +- assets/js/{3f3d2568.203d5bc1.js => 3f3d2568.ef89c82f.js} | 2 +- assets/js/{4541de2f.46364c4f.js => 4541de2f.3f777449.js} | 2 +- assets/js/{61ce1e27.8ca31dc7.js => 61ce1e27.d16b23c2.js} | 2 +- assets/js/{a37a44be.7206509d.js => a37a44be.5fe16789.js} | 2 +- assets/js/{dc9e5b6f.6d1923d4.js => dc9e5b6f.86af65ec.js} | 2 +- assets/js/{fa2418a3.56863c34.js => fa2418a3.55c7f797.js} | 2 +- ...{runtime~main.67daed6d.js => runtime~main.22f4478a.js} | 2 +- blog/2021/10/14/roadmap/index.html | 4 ++-- blog/2022/02/21/js-flipper-announcement/index.html | 4 ++-- blog/2022/05/20/preparing-for-headless-flipper/index.html | 4 ++-- blog/archive/index.html | 4 ++-- blog/index.html | 4 ++-- blog/tags/flipper/index.html | 4 ++-- blog/tags/headless/index.html | 4 ++-- blog/tags/index.html | 4 ++-- blog/tags/node-js/index.html | 4 ++-- blog/tags/plugins/index.html | 4 ++-- blog/tags/react-native/index.html | 4 ++-- blog/tags/react/index.html | 4 ++-- blog/tags/web/index.html | 4 ++-- docs/custom-ports/index.html | 4 ++-- docs/extending/arch/index.html | 4 ++-- docs/extending/client-plugin-lifecycle/index.html | 4 ++-- docs/extending/create-plugin/index.html | 4 ++-- docs/extending/debugging/index.html | 4 ++-- docs/extending/deeplinks/index.html | 4 ++-- docs/extending/desktop-plugin-structure/index.html | 4 ++-- docs/extending/dev-setup/index.html | 4 ++-- docs/extending/error-handling/index.html | 4 ++-- docs/extending/establishing-a-connection/index.html | 4 ++-- docs/extending/flipper-plugin/index.html | 4 ++-- docs/extending/layout-inspector/index.html | 4 ++-- docs/extending/loading-custom-plugins/index.html | 4 ++-- docs/extending/new-clients/index.html | 4 ++-- docs/extending/node-apis/index.html | 4 ++-- docs/extending/plugin-distribution/index.html | 4 ++-- docs/extending/power-search/index.html | 4 ++-- docs/extending/public-releases/index.html | 4 ++-- docs/extending/sandy-migration/index.html | 4 ++-- docs/extending/style-guide/index.html | 4 ++-- docs/extending/styling-components/index.html | 4 ++-- docs/extending/supporting-layout/index.html | 4 ++-- docs/extending/testing-rn/index.html | 4 ++-- docs/extending/testing/index.html | 4 ++-- docs/features/index.html | 4 ++-- docs/features/plugins/crash-reporter/index.html | 4 ++-- docs/features/plugins/databases/index.html | 4 ++-- docs/features/plugins/device-logs/index.html | 4 ++-- docs/features/plugins/fresco/index.html | 4 ++-- docs/features/plugins/inspector/index.html | 4 ++-- docs/features/plugins/leak-canary/index.html | 4 ++-- docs/features/plugins/navigation/index.html | 4 ++-- docs/features/plugins/network/index.html | 4 ++-- docs/features/plugins/preferences/index.html | 4 ++-- docs/features/plugins/sandbox/index.html | 4 ++-- docs/features/plugins/ui-debugger/index.html | 4 ++-- docs/features/react-native/index.html | 4 ++-- docs/features/share-flipper-data/index.html | 4 ++-- docs/features/virtual-devices/index.html | 4 ++-- docs/getting-started/android-native/index.html | 8 ++++---- docs/getting-started/index.html | 4 ++-- docs/getting-started/ios-native/index.html | 4 ++-- docs/getting-started/javascript/index.html | 4 ++-- docs/getting-started/react-native-android/index.html | 4 ++-- docs/getting-started/react-native-ios/index.html | 6 +++--- docs/getting-started/react-native/index.html | 6 +++--- docs/getting-started/troubleshooting/android/index.html | 4 ++-- docs/getting-started/troubleshooting/general/index.html | 4 ++-- docs/getting-started/troubleshooting/index.html | 4 ++-- .../troubleshooting/install-android-sdk/index.html | 4 ++-- .../troubleshooting/install-ios-sdk/index.html | 4 ++-- docs/getting-started/troubleshooting/ios/index.html | 4 ++-- .../troubleshooting/react-native/index.html | 4 ++-- docs/internals/contributing/index.html | 4 ++-- docs/internals/device-identifiers/index.html | 4 ++-- docs/internals/documentation-formatting/index.html | 4 ++-- docs/internals/documentation-standards/index.html | 4 ++-- docs/internals/documentation-writing-guide/index.html | 4 ++-- docs/internals/index.html | 4 ++-- docs/internals/linters/index.html | 4 ++-- docs/plugins/crash-reporter/overview/index.html | 4 ++-- docs/plugins/crash-reporter/setup/index.html | 4 ++-- docs/plugins/databases/overview/index.html | 4 ++-- docs/plugins/databases/setup/index.html | 4 ++-- docs/plugins/device-logs/overview/index.html | 4 ++-- docs/plugins/fresco/overview/index.html | 4 ++-- docs/plugins/fresco/setup/index.html | 4 ++-- docs/plugins/inspector/overview/index.html | 4 ++-- docs/plugins/inspector/setup/index.html | 6 +++--- docs/plugins/leak-canary/overview/index.html | 4 ++-- docs/plugins/leak-canary/setup/index.html | 6 +++--- docs/plugins/navigation/overview/index.html | 4 ++-- docs/plugins/navigation/setup/index.html | 4 ++-- docs/plugins/network/overview/index.html | 4 ++-- docs/plugins/network/setup/index.html | 6 +++--- docs/plugins/preferences/overview/index.html | 4 ++-- docs/plugins/preferences/setup/index.html | 4 ++-- docs/plugins/sandbox/overview/index.html | 4 ++-- docs/plugins/sandbox/setup/index.html | 4 ++-- docs/plugins/ui-debugger/overview/index.html | 4 ++-- docs/setup/plugins/crash-reporter/index.html | 4 ++-- docs/setup/plugins/databases/index.html | 4 ++-- docs/setup/plugins/fresco/index.html | 4 ++-- docs/setup/plugins/inspector/index.html | 6 +++--- docs/setup/plugins/leak-canary/index.html | 6 +++--- docs/setup/plugins/navigation/index.html | 4 ++-- docs/setup/plugins/network/index.html | 6 +++--- docs/setup/plugins/preferences/index.html | 4 ++-- docs/setup/plugins/sandbox/index.html | 4 ++-- docs/stetho/index.html | 4 ++-- docs/tutorial/android/index.html | 4 ++-- docs/tutorial/intro/index.html | 4 ++-- docs/tutorial/ios/index.html | 4 ++-- docs/tutorial/javascript/index.html | 4 ++-- docs/tutorial/js-custom/index.html | 4 ++-- docs/tutorial/js-publishing/index.html | 4 ++-- docs/tutorial/js-setup/index.html | 4 ++-- docs/tutorial/js-table/index.html | 4 ++-- docs/tutorial/marketplace/index.html | 4 ++-- docs/tutorial/react-native/index.html | 4 ++-- index.html | 4 ++-- search/index.html | 4 ++-- 129 files changed, 259 insertions(+), 259 deletions(-) rename assets/js/{0fc226a0.61b1ee64.js => 0fc226a0.1a542511.js} (99%) rename assets/js/{303bd344.e771e401.js => 303bd344.9a474805.js} (99%) rename assets/js/{3a952f51.620f906d.js => 3a952f51.c7de4763.js} (98%) rename assets/js/{3f3d2568.203d5bc1.js => 3f3d2568.ef89c82f.js} (99%) rename assets/js/{4541de2f.46364c4f.js => 4541de2f.3f777449.js} (99%) rename assets/js/{61ce1e27.8ca31dc7.js => 61ce1e27.d16b23c2.js} (98%) rename assets/js/{a37a44be.7206509d.js => a37a44be.5fe16789.js} (99%) rename assets/js/{dc9e5b6f.6d1923d4.js => dc9e5b6f.86af65ec.js} (98%) rename assets/js/{fa2418a3.56863c34.js => fa2418a3.55c7f797.js} (98%) rename assets/js/{runtime~main.67daed6d.js => runtime~main.22f4478a.js} (93%) diff --git a/404.html b/404.html index b74a6226c29..778fc28bc1b 100644 --- a/404.html +++ b/404.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/_src/getting-started/android-native.mdx b/_src/getting-started/android-native.mdx index 02e17eae9f5..31906d262ad 100644 --- a/_src/getting-started/android-native.mdx +++ b/_src/getting-started/android-native.mdx @@ -24,10 +24,10 @@ repositories { } dependencies { - debugImplementation 'com.facebook.flipper:flipper:0.259.0' + debugImplementation 'com.facebook.flipper:flipper:0.260.0' debugImplementation 'com.facebook.soloader:soloader:0.10.5' - releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.0' + releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.0' } ``` @@ -124,10 +124,10 @@ repositories { } dependencies { - debugImplementation 'com.facebook.flipper:flipper:0.259.1-SNAPSHOT' + debugImplementation 'com.facebook.flipper:flipper:0.260.1-SNAPSHOT' debugImplementation 'com.facebook.soloader:soloader:0.10.5' - releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.1-SNAPSHOT' + releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.1-SNAPSHOT' } ``` diff --git a/_src/getting-started/react-native-ios.mdx b/_src/getting-started/react-native-ios.mdx index fb16681f44e..74b2ab1bc4d 100644 --- a/_src/getting-started/react-native-ios.mdx +++ b/_src/getting-started/react-native-ios.mdx @@ -51,7 +51,7 @@ Add all of the code below to your `ios/Podfile`: platform :ios, '9.0' def flipper_pods() - flipperkit_version = '0.259.0' # should match the version of your Flipper client app + flipperkit_version = '0.260.0' # should match the version of your Flipper client app pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug' pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug' pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug' diff --git a/_src/getting-started/react-native.mdx b/_src/getting-started/react-native.mdx index 0cd0ef9689a..ee92548eeb9 100644 --- a/_src/getting-started/react-native.mdx +++ b/_src/getting-started/react-native.mdx @@ -34,7 +34,7 @@ Latest version of Flipper requires react-native 0.69+! If you use react-native < Android: -1. Bump the `FLIPPER_VERSION` variable in `android/gradle.properties`, for example: `FLIPPER_VERSION=0.259.0`. +1. Bump the `FLIPPER_VERSION` variable in `android/gradle.properties`, for example: `FLIPPER_VERSION=0.260.0`. 2. Run `./gradlew clean` in the `android` directory. iOS: @@ -44,7 +44,7 @@ react-native version => 0.69.0 2. Run `pod install --repo-update` in the `ios` directory. react-native version < 0.69.0 -1. Call `use_flipper` with a specific version in `ios/Podfile`, for example: `use_flipper!({ 'Flipper' => '0.259.0' })`. +1. Call `use_flipper` with a specific version in `ios/Podfile`, for example: `use_flipper!({ 'Flipper' => '0.260.0' })`. 2. Run `pod install --repo-update` in the `ios` directory. ## Manual Setup diff --git a/assets/js/0fc226a0.61b1ee64.js b/assets/js/0fc226a0.1a542511.js similarity index 99% rename from assets/js/0fc226a0.61b1ee64.js rename to assets/js/0fc226a0.1a542511.js index a5967d535fb..4d26432c030 100644 --- a/assets/js/0fc226a0.61b1ee64.js +++ b/assets/js/0fc226a0.1a542511.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[972],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>p,MDXProvider:()=>d,mdx:()=>v,useMDXComponents:()=>c,withMDXComponents:()=>s});var i=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(){return r=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=i.createContext({}),s=function(e){return function(t){var n=c(t.components);return i.createElement(e,r({},t,{components:n}))}},c=function(e){var t=i.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=c(e.components);return i.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},m=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),s=c(n),d=a,m=s["".concat(l,".").concat(d)]||s[d]||f[d]||r;return n?i.createElement(m,o(o({ref:t},p),{},{components:n})):i.createElement(m,o({ref:t},p))}));function v(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=m;var o={};for(var u in t)hasOwnProperty.call(t,u)&&(o[u]=t[u]);o.originalType=e,o.mdxType="string"==typeof e?e:a,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{default:()=>l});var i=n(67294),a=n(86010);const r="tabItem_Ymn6";function l(e){var t=e.children,n=e.hidden,l=e.className;return i.createElement("div",{role:"tabpanel",className:(0,a.default)(r,l),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>x});var i=n(83117),a=n(67294),r=n(86010),l=n(12466),o=n(76775),u=n(91980),p=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,a.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,n=void 0!==t&&t,i=e.groupId,r=(0,o.k6)(),l=function(e){var t=e.queryString,n=void 0!==t&&t,i=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!i)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=i?i:null}({queryString:n,groupId:i});return[(0,u._X)(l),(0,a.useCallback)((function(e){if(l){var t=new URLSearchParams(r.location.search);t.set(l,e),r.replace(Object.assign({},r.location,{search:t.toString()}))}}),[l,r])]}function v(e){var t,n,i,r,l=e.defaultValue,o=e.queryString,u=void 0!==o&&o,p=e.groupId,c=d(e),v=(0,a.useState)((function(){return function(e){var t,n=e.defaultValue,i=e.tabValues;if(0===i.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!f({value:n,tabValues:i}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+i.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var a=null!=(t=i.find((function(e){return e.default})))?t:i[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:l,tabValues:c})})),g=v[0],h=v[1],b=m({queryString:u,groupId:p}),y=b[0],N=b[1],w=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),n=(0,s.Nk)(t),i=n[0],r=n[1],[i,(0,a.useCallback)((function(e){t&&r.set(e)}),[t,r])]),x=w[0],_=w[1],F=function(){var e=null!=y?y:x;return f({value:e,tabValues:c})?e:null}();return(0,a.useLayoutEffect)((function(){F&&h(F)}),[F]),{selectedValue:g,selectValue:(0,a.useCallback)((function(e){if(!f({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);h(e),N(e),_(e)}),[N,_,c]),tabValues:c}}var g=n(72389);const h="tabList__CuJ",b="tabItem_LNqP";function y(e){var t=e.className,n=e.block,o=e.selectedValue,u=e.selectValue,p=e.tabValues,s=[],c=(0,l.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),i=p[n].value;i!==o&&(c(t),u(i))},f=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var i,a=s.indexOf(e.currentTarget)+1;n=null!=(i=s[a])?i:s[0];break;case"ArrowLeft":var r,l=s.indexOf(e.currentTarget)-1;n=null!=(r=s[l])?r:s[s.length-1]}null==(t=n)||t.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.default)("tabs",{"tabs--block":n},t)},p.map((function(e){var t=e.value,n=e.label,l=e.attributes;return a.createElement("li",(0,i.Z)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:function(e){return s.push(e)},onKeyDown:f,onClick:d},l,{className:(0,r.default)("tabs__item",b,null==l?void 0:l.className,{"tabs__item--active":o===t})}),null!=n?n:t)})))}function N(e){var t=e.lazy,n=e.children,i=e.selectedValue,r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var l=r.find((function(e){return e.props.value===i}));return l?(0,a.cloneElement)(l,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},r.map((function(e,t){return(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==i})})))}function w(e){var t=v(e);return a.createElement("div",{className:(0,r.default)("tabs-container",h)},a.createElement(y,(0,i.Z)({},e,t)),a.createElement(N,(0,i.Z)({},e,t)))}function x(e){var t=(0,g.default)();return a.createElement(w,(0,i.Z)({key:String(t)},e))}},81477:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>m,contentTitle:()=>d,default:()=>h,frontMatter:()=>c,metadata:()=>f,toc:()=>v});var i=n(83117),a=n(80102),r=(n(67294),n(3905)),l=n(44996),o=n(39960),u=n(74866),p=n(85162),s=["components"],c={id:"react-native-ios",title:"React Native - Manual iOS Setup",sidebar_label:"Manual iOS Setup"},d=void 0,f={unversionedId:"getting-started/react-native-ios",id:"getting-started/react-native-ios",title:"React Native - Manual iOS Setup",description:"These details within this page are for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the",source:"@site/../docs/getting-started/react-native-ios.mdx",sourceDirName:"getting-started",slug:"/getting-started/react-native-ios",permalink:"/docs/getting-started/react-native-ios",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/react-native-ios.mdx",tags:[],version:"current",frontMatter:{id:"react-native-ios",title:"React Native - Manual iOS Setup",sidebar_label:"Manual iOS Setup"},sidebar:"main",previous:{title:"Manual Android Setup",permalink:"/docs/getting-started/react-native-android"},next:{title:"JavaScript (browser / Node.js)",permalink:"/docs/getting-started/javascript"}},m={},v=[{value:"Dependencies",id:"dependencies",level:2},{value:"React Native 0.63+",id:"react-native-063",level:3},{value:"React Native 0.62",id:"react-native-062",level:3},{value:"Initialization",id:"initialization",level:2},{value:"React Native 0.68+",id:"react-native-068",level:3},{value:"React Native 0.67",id:"react-native-067",level:3},{value:"Issues or questions",id:"issues-or-questions",level:2},{value:"Further Steps",id:"further-steps",level:2}],g={toc:v};function h(e){var t=e.components,n=(0,a.Z)(e,s);return(0,r.mdx)("wrapper",(0,i.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,r.mdx)("admonition",{type:"note"},(0,r.mdx)("p",{parentName:"admonition"},"These details within this page are for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the\n",(0,r.mdx)("a",{parentName:"p",href:"https://reactnative.dev/docs/upgrading"},"React Native Upgrade tool"),".")),(0,r.mdx)("h2",{id:"dependencies"},"Dependencies"),(0,r.mdx)("h3",{id:"react-native-063"},"React Native 0.63+"),(0,r.mdx)("p",null,"If using React Native 0.63 or later, your ",(0,r.mdx)("inlineCode",{parentName:"p"},"ios/Podfile")," should look like this:"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-ruby"},"platform :ios, '10.0'\n\nrequire_relative '../node_modules/react-native/scripts/react_native_pods'\nrequire_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'\n\ntarget 'your-app-name' do\n config = use_native_modules!\n use_react_native!(path: config['reactNativePath'])\n\n # Enables Flipper.\n #\n # Note that if you have use_frameworks! enabled, Flipper will not work and\n # you should disable these next few lines.\n use_flipper!({'Flipper' => '0.58.0'}) # should match the version of your Flipper client app\n post_install do |installer|\n flipper_post_install(installer)\n end\nend\n")),(0,r.mdx)("p",null,"Install the dependencies by running ",(0,r.mdx)("inlineCode",{parentName:"p"},"cd ios && pod install")," then continue to ",(0,r.mdx)("a",{parentName:"p",href:"#initialization"},"Initialization"),"."),(0,r.mdx)("h3",{id:"react-native-062"},"React Native 0.62"),(0,r.mdx)("p",null,"In version 0.62, the setup includes a bit more code (which was moved to a helper in 0.63).\nAdd all of the code below to your ",(0,r.mdx)("inlineCode",{parentName:"p"},"ios/Podfile"),":"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-ruby"},"platform :ios, '9.0'\n\ndef flipper_pods()\n flipperkit_version = '0.259.0' # should match the version of your Flipper client app\n pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitReactPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\nend\n\n# Post Install processing for Flipper\ndef flipper_post_install(installer)\n file_name = Dir.glob(\"*.xcodeproj\")[0]\n app_project = Xcodeproj::Project.open(file_name)\n app_project.native_targets.each do |target|\n target.build_configurations.each do |config|\n cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '\n unless cflags.include? '-DFB_SONARKIT_ENABLED=1'\n puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'\n cflags << '-DFB_SONARKIT_ENABLED=1'\n end\n config.build_settings['OTHER_CFLAGS'] = cflags\n end\n app_project.save\n end\n installer.pods_project.save\nend\n\ntarget 'your-app-name' do\n ...\n # Replace the existing yoga import with the following (adding modular_headers):\n pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true\n ...\n use_native_modules!\n\n # For enabling Flipper.\n # Note that if you use_framework!, flipper will not work.\n # Disable these lines if you are doing use_framework!\n flipper_pods()\n post_install do |installer|\n flipper_post_install(installer)\n end\nend\n")),(0,r.mdx)("p",null,"Install the dependencies by running ",(0,r.mdx)("inlineCode",{parentName:"p"},"cd ios && pod install"),". You can now import and initialize Flipper in your AppDelegate."),(0,r.mdx)("h2",{id:"initialization"},"Initialization"),(0,r.mdx)("p",null,"The code below enables the following integrations:"),(0,r.mdx)("ul",null,(0,r.mdx)("li",{parentName:"ul"},"Layout Inspector"),(0,r.mdx)("li",{parentName:"ul"},"Network"),(0,r.mdx)("li",{parentName:"ul"},"Shared Preferences"),(0,r.mdx)("li",{parentName:"ul"},"Crash Reporter")),(0,r.mdx)("h3",{id:"react-native-068"},"React Native 0.68+"),(0,r.mdx)("p",null,"If using React Native 0.68 or later, your AppDelegate should include"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-objc"},"...\n#import \n")),(0,r.mdx)("p",null,"RCTAppSetupUtils takes care of initializing Flipper and the integrations mentioned above."),(0,r.mdx)("h3",{id:"react-native-067"},"React Native 0.67"),(0,r.mdx)(u.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,r.mdx)(p.default,{value:"ios",mdxType:"TabItem"},(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-objc"},"...\n#if DEBUG\n#ifdef FB_SONARKIT_ENABLED\n#import \n#import \n#import \n#import \n#import \n#import \n#import \n#endif\n#endif\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions\n{\n [self initializeFlipper:application];\n ...\n}\n\n- (void) initializeFlipper:(UIApplication *)application {\n #if DEBUG\n #ifdef FB_SONARKIT_ENABLED\n FlipperClient *client = [FlipperClient sharedClient];\n SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n [client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application withDescriptorMapper: layoutDescriptorMapper]];\n [client addPlugin: [[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];\n [client addPlugin: [FlipperKitReactPlugin new]];\n [client addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n [client start];\n #endif\n #endif\n}\n\n@end\n"))),(0,r.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-swift"},"...\n#if DEBUG\n#ifdef FB_SONARKIT_ENABLED\nimport FlipperKit\n#endif\n#endif\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n func application(\n _ application: UIApplication,\n didFinishLaunchingWithOptions\n launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n ) -> Bool {\n initializeFlipper(with: application)\n ...\n }\n\n private func initializeFlipper(with application: UIApplication) {\n #if DEBUG\n #ifdef FB_SONARKIT_ENABLED\n let client = FlipperClient.shared()\n let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\n FlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n client?.add(FKUserDefaultsPlugin(suiteName: nil))\n client?.add(FlipperKitReactPlugin())\n client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n client?.add(FlipperReactPerformancePlugin.sharedInstance())\n client?.start()\n #endif\n #endif\n }\n}\n")))),(0,r.mdx)("p",null,"Lastly, open the Flipper desktop app, and run ",(0,r.mdx)("inlineCode",{parentName:"p"},"yarn ios")," in your terminal."),(0,r.mdx)("h2",{id:"issues-or-questions"},"Issues or questions"),(0,r.mdx)("p",null,"If you encounter any issues or have any questions, refer to the ",(0,r.mdx)("a",{parentName:"p",href:"/docs/getting-started/troubleshooting/"},"Troubleshooting")," section."),(0,r.mdx)("h2",{id:"further-steps"},"Further Steps"),(0,r.mdx)("p",null,"To create your own plugins and integrate with Flipper using JavaScript, check out our ",(0,r.mdx)(o.default,{to:(0,l.default)("/docs/tutorial/react-native"),mdxType:"Link"},"Building a React Native Plugin")," tutorial!"))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[972],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>p,MDXProvider:()=>d,mdx:()=>v,useMDXComponents:()=>c,withMDXComponents:()=>s});var i=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(){return r=Object.assign||function(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=i.createContext({}),s=function(e){return function(t){var n=c(t.components);return i.createElement(e,r({},t,{components:n}))}},c=function(e){var t=i.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},d=function(e){var t=c(e.components);return i.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},m=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),s=c(n),d=a,m=s["".concat(l,".").concat(d)]||s[d]||f[d]||r;return n?i.createElement(m,o(o({ref:t},p),{},{components:n})):i.createElement(m,o({ref:t},p))}));function v(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=m;var o={};for(var u in t)hasOwnProperty.call(t,u)&&(o[u]=t[u]);o.originalType=e,o.mdxType="string"==typeof e?e:a,l[1]=o;for(var p=2;p{n.r(t),n.d(t,{default:()=>l});var i=n(67294),a=n(86010);const r="tabItem_Ymn6";function l(e){var t=e.children,n=e.hidden,l=e.className;return i.createElement("div",{role:"tabpanel",className:(0,a.default)(r,l),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>x});var i=n(83117),a=n(67294),r=n(86010),l=n(12466),o=n(76775),u=n(91980),p=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,a.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,n=void 0!==t&&t,i=e.groupId,r=(0,o.k6)(),l=function(e){var t=e.queryString,n=void 0!==t&&t,i=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!i)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=i?i:null}({queryString:n,groupId:i});return[(0,u._X)(l),(0,a.useCallback)((function(e){if(l){var t=new URLSearchParams(r.location.search);t.set(l,e),r.replace(Object.assign({},r.location,{search:t.toString()}))}}),[l,r])]}function v(e){var t,n,i,r,l=e.defaultValue,o=e.queryString,u=void 0!==o&&o,p=e.groupId,c=d(e),v=(0,a.useState)((function(){return function(e){var t,n=e.defaultValue,i=e.tabValues;if(0===i.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!f({value:n,tabValues:i}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+i.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var a=null!=(t=i.find((function(e){return e.default})))?t:i[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:l,tabValues:c})})),g=v[0],h=v[1],b=m({queryString:u,groupId:p}),y=b[0],N=b[1],w=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),n=(0,s.Nk)(t),i=n[0],r=n[1],[i,(0,a.useCallback)((function(e){t&&r.set(e)}),[t,r])]),x=w[0],_=w[1],F=function(){var e=null!=y?y:x;return f({value:e,tabValues:c})?e:null}();return(0,a.useLayoutEffect)((function(){F&&h(F)}),[F]),{selectedValue:g,selectValue:(0,a.useCallback)((function(e){if(!f({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);h(e),N(e),_(e)}),[N,_,c]),tabValues:c}}var g=n(72389);const h="tabList__CuJ",b="tabItem_LNqP";function y(e){var t=e.className,n=e.block,o=e.selectedValue,u=e.selectValue,p=e.tabValues,s=[],c=(0,l.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),i=p[n].value;i!==o&&(c(t),u(i))},f=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var i,a=s.indexOf(e.currentTarget)+1;n=null!=(i=s[a])?i:s[0];break;case"ArrowLeft":var r,l=s.indexOf(e.currentTarget)-1;n=null!=(r=s[l])?r:s[s.length-1]}null==(t=n)||t.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.default)("tabs",{"tabs--block":n},t)},p.map((function(e){var t=e.value,n=e.label,l=e.attributes;return a.createElement("li",(0,i.Z)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:function(e){return s.push(e)},onKeyDown:f,onClick:d},l,{className:(0,r.default)("tabs__item",b,null==l?void 0:l.className,{"tabs__item--active":o===t})}),null!=n?n:t)})))}function N(e){var t=e.lazy,n=e.children,i=e.selectedValue,r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var l=r.find((function(e){return e.props.value===i}));return l?(0,a.cloneElement)(l,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},r.map((function(e,t){return(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==i})})))}function w(e){var t=v(e);return a.createElement("div",{className:(0,r.default)("tabs-container",h)},a.createElement(y,(0,i.Z)({},e,t)),a.createElement(N,(0,i.Z)({},e,t)))}function x(e){var t=(0,g.default)();return a.createElement(w,(0,i.Z)({key:String(t)},e))}},81477:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>m,contentTitle:()=>d,default:()=>h,frontMatter:()=>c,metadata:()=>f,toc:()=>v});var i=n(83117),a=n(80102),r=(n(67294),n(3905)),l=n(44996),o=n(39960),u=n(74866),p=n(85162),s=["components"],c={id:"react-native-ios",title:"React Native - Manual iOS Setup",sidebar_label:"Manual iOS Setup"},d=void 0,f={unversionedId:"getting-started/react-native-ios",id:"getting-started/react-native-ios",title:"React Native - Manual iOS Setup",description:"These details within this page are for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the",source:"@site/../docs/getting-started/react-native-ios.mdx",sourceDirName:"getting-started",slug:"/getting-started/react-native-ios",permalink:"/docs/getting-started/react-native-ios",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/react-native-ios.mdx",tags:[],version:"current",frontMatter:{id:"react-native-ios",title:"React Native - Manual iOS Setup",sidebar_label:"Manual iOS Setup"},sidebar:"main",previous:{title:"Manual Android Setup",permalink:"/docs/getting-started/react-native-android"},next:{title:"JavaScript (browser / Node.js)",permalink:"/docs/getting-started/javascript"}},m={},v=[{value:"Dependencies",id:"dependencies",level:2},{value:"React Native 0.63+",id:"react-native-063",level:3},{value:"React Native 0.62",id:"react-native-062",level:3},{value:"Initialization",id:"initialization",level:2},{value:"React Native 0.68+",id:"react-native-068",level:3},{value:"React Native 0.67",id:"react-native-067",level:3},{value:"Issues or questions",id:"issues-or-questions",level:2},{value:"Further Steps",id:"further-steps",level:2}],g={toc:v};function h(e){var t=e.components,n=(0,a.Z)(e,s);return(0,r.mdx)("wrapper",(0,i.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,r.mdx)("admonition",{type:"note"},(0,r.mdx)("p",{parentName:"admonition"},"These details within this page are for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the\n",(0,r.mdx)("a",{parentName:"p",href:"https://reactnative.dev/docs/upgrading"},"React Native Upgrade tool"),".")),(0,r.mdx)("h2",{id:"dependencies"},"Dependencies"),(0,r.mdx)("h3",{id:"react-native-063"},"React Native 0.63+"),(0,r.mdx)("p",null,"If using React Native 0.63 or later, your ",(0,r.mdx)("inlineCode",{parentName:"p"},"ios/Podfile")," should look like this:"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-ruby"},"platform :ios, '10.0'\n\nrequire_relative '../node_modules/react-native/scripts/react_native_pods'\nrequire_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'\n\ntarget 'your-app-name' do\n config = use_native_modules!\n use_react_native!(path: config['reactNativePath'])\n\n # Enables Flipper.\n #\n # Note that if you have use_frameworks! enabled, Flipper will not work and\n # you should disable these next few lines.\n use_flipper!({'Flipper' => '0.58.0'}) # should match the version of your Flipper client app\n post_install do |installer|\n flipper_post_install(installer)\n end\nend\n")),(0,r.mdx)("p",null,"Install the dependencies by running ",(0,r.mdx)("inlineCode",{parentName:"p"},"cd ios && pod install")," then continue to ",(0,r.mdx)("a",{parentName:"p",href:"#initialization"},"Initialization"),"."),(0,r.mdx)("h3",{id:"react-native-062"},"React Native 0.62"),(0,r.mdx)("p",null,"In version 0.62, the setup includes a bit more code (which was moved to a helper in 0.63).\nAdd all of the code below to your ",(0,r.mdx)("inlineCode",{parentName:"p"},"ios/Podfile"),":"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-ruby"},"platform :ios, '9.0'\n\ndef flipper_pods()\n flipperkit_version = '0.260.0' # should match the version of your Flipper client app\n pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\n pod 'FlipperKit/FlipperKitReactPlugin', '~>' + flipperkit_version, :configuration => 'Debug'\nend\n\n# Post Install processing for Flipper\ndef flipper_post_install(installer)\n file_name = Dir.glob(\"*.xcodeproj\")[0]\n app_project = Xcodeproj::Project.open(file_name)\n app_project.native_targets.each do |target|\n target.build_configurations.each do |config|\n cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '\n unless cflags.include? '-DFB_SONARKIT_ENABLED=1'\n puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'\n cflags << '-DFB_SONARKIT_ENABLED=1'\n end\n config.build_settings['OTHER_CFLAGS'] = cflags\n end\n app_project.save\n end\n installer.pods_project.save\nend\n\ntarget 'your-app-name' do\n ...\n # Replace the existing yoga import with the following (adding modular_headers):\n pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true\n ...\n use_native_modules!\n\n # For enabling Flipper.\n # Note that if you use_framework!, flipper will not work.\n # Disable these lines if you are doing use_framework!\n flipper_pods()\n post_install do |installer|\n flipper_post_install(installer)\n end\nend\n")),(0,r.mdx)("p",null,"Install the dependencies by running ",(0,r.mdx)("inlineCode",{parentName:"p"},"cd ios && pod install"),". You can now import and initialize Flipper in your AppDelegate."),(0,r.mdx)("h2",{id:"initialization"},"Initialization"),(0,r.mdx)("p",null,"The code below enables the following integrations:"),(0,r.mdx)("ul",null,(0,r.mdx)("li",{parentName:"ul"},"Layout Inspector"),(0,r.mdx)("li",{parentName:"ul"},"Network"),(0,r.mdx)("li",{parentName:"ul"},"Shared Preferences"),(0,r.mdx)("li",{parentName:"ul"},"Crash Reporter")),(0,r.mdx)("h3",{id:"react-native-068"},"React Native 0.68+"),(0,r.mdx)("p",null,"If using React Native 0.68 or later, your AppDelegate should include"),(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-objc"},"...\n#import \n")),(0,r.mdx)("p",null,"RCTAppSetupUtils takes care of initializing Flipper and the integrations mentioned above."),(0,r.mdx)("h3",{id:"react-native-067"},"React Native 0.67"),(0,r.mdx)(u.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,r.mdx)(p.default,{value:"ios",mdxType:"TabItem"},(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-objc"},"...\n#if DEBUG\n#ifdef FB_SONARKIT_ENABLED\n#import \n#import \n#import \n#import \n#import \n#import \n#import \n#endif\n#endif\n\n@implementation AppDelegate\n\n- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions\n{\n [self initializeFlipper:application];\n ...\n}\n\n- (void) initializeFlipper:(UIApplication *)application {\n #if DEBUG\n #ifdef FB_SONARKIT_ENABLED\n FlipperClient *client = [FlipperClient sharedClient];\n SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n [client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application withDescriptorMapper: layoutDescriptorMapper]];\n [client addPlugin: [[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];\n [client addPlugin: [FlipperKitReactPlugin new]];\n [client addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n [client start];\n #endif\n #endif\n}\n\n@end\n"))),(0,r.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,r.mdx)("pre",null,(0,r.mdx)("code",{parentName:"pre",className:"language-swift"},"...\n#if DEBUG\n#ifdef FB_SONARKIT_ENABLED\nimport FlipperKit\n#endif\n#endif\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n func application(\n _ application: UIApplication,\n didFinishLaunchingWithOptions\n launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n ) -> Bool {\n initializeFlipper(with: application)\n ...\n }\n\n private func initializeFlipper(with application: UIApplication) {\n #if DEBUG\n #ifdef FB_SONARKIT_ENABLED\n let client = FlipperClient.shared()\n let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\n FlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n client?.add(FKUserDefaultsPlugin(suiteName: nil))\n client?.add(FlipperKitReactPlugin())\n client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n client?.add(FlipperReactPerformancePlugin.sharedInstance())\n client?.start()\n #endif\n #endif\n }\n}\n")))),(0,r.mdx)("p",null,"Lastly, open the Flipper desktop app, and run ",(0,r.mdx)("inlineCode",{parentName:"p"},"yarn ios")," in your terminal."),(0,r.mdx)("h2",{id:"issues-or-questions"},"Issues or questions"),(0,r.mdx)("p",null,"If you encounter any issues or have any questions, refer to the ",(0,r.mdx)("a",{parentName:"p",href:"/docs/getting-started/troubleshooting/"},"Troubleshooting")," section."),(0,r.mdx)("h2",{id:"further-steps"},"Further Steps"),(0,r.mdx)("p",null,"To create your own plugins and integrate with Flipper using JavaScript, check out our ",(0,r.mdx)(o.default,{to:(0,l.default)("/docs/tutorial/react-native"),mdxType:"Link"},"Building a React Native Plugin")," tutorial!"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/303bd344.e771e401.js b/assets/js/303bd344.9a474805.js similarity index 99% rename from assets/js/303bd344.e771e401.js rename to assets/js/303bd344.9a474805.js index 50dded2ea82..871de00c005 100644 --- a/assets/js/303bd344.e771e401.js +++ b/assets/js/303bd344.9a474805.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6033],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>u,MDXProvider:()=>d,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>s});var r=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=r.createContext({}),s=function(e){return function(t){var n=c(t.components);return r.createElement(e,a({},t,{components:n}))}},c=function(e){var t=r.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return r.createElement(u.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,o=e.parentName,u=p(e,["components","mdxType","originalType","parentName"]),s=c(n),d=i,f=s["".concat(o,".").concat(d)]||s[d]||m[d]||a;return n?r.createElement(f,l(l({ref:t},u),{},{components:n})):r.createElement(f,l({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:i,o[1]=l;for(var u=2;u{n.r(t),n.d(t,{contentTitle:()=>d,default:()=>v,frontMatter:()=>c,metadata:()=>m,toc:()=>f});var r=n(83117),i=n(80102),a=(n(67294),n(3905)),o=n(44996),l=n(39960),p=n(74866),u=n(85162),s=["components"],c={},d=void 0,m={type:"mdx",permalink:"/docs/plugins/inspector/setup",source:"@site/src/embedded-pages/docs/plugins/inspector/setup.mdx",description:"To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},f=[{value:"Android",id:"android",level:2},{value:"Standard Android view only",id:"standard-android-view-only",level:3},{value:"With Litho Support",id:"with-litho-support",level:3},{value:"Blocking fullscreen views (Android only)",id:"blocking-fullscreen-views-android-only",level:3},{value:"Blocking empty view groups (Android only)",id:"blocking-empty-view-groups-android-only",level:3},{value:"iOS",id:"ios",level:2},{value:"Standard UIView Only",id:"standard-uiview-only",level:3},{value:"With ComponentKit Support",id:"with-componentkit-support",level:3}],h={toc:f};function v(e){var t=e.components,n=(0,i.Z)(e,s);return(0,a.mdx)("wrapper",(0,r.Z)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"To use the ",(0,a.mdx)(l.default,{to:(0,o.default)("/docs/features/plugins/inspector"),mdxType:"Link"},"Layout Inspector plugin"),", you need to add the plugin to your Flipper client instance."),(0,a.mdx)("h2",{id:"android"},"Android"),(0,a.mdx)("h3",{id:"standard-android-view-only"},"Standard Android view only"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,a.mdx)("h3",{id:"with-litho-support"},"With Litho Support"),(0,a.mdx)("p",null,"Litho support is provided via an optional plugin."),(0,a.mdx)("p",null,"You also need to compile in the ",(0,a.mdx)("inlineCode",{parentName:"p"},"litho-annotations")," package, as Flipper reflects on them at runtime. So ensure to not just include them as ",(0,a.mdx)("inlineCode",{parentName:"p"},"compileOnly")," in your gradle configuration:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.259.0'\n debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'\n // ...\n}\n")),(0,a.mdx)("p",null,"If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies."),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.litho.config.ComponentsConfiguration;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\nimport com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;\n\n// Instead of hard-coding this setting, it's a good practice to tie\n// this to a BuildConfig flag, that you only enable for debug builds\n// of your application.\nComponentsConfiguration.isDebugModeEnabled = true;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n// This adds Litho capabilities to the layout inspector.\nLithoFlipperDescriptors.add(descriptorMapping);\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,a.mdx)("h3",{id:"blocking-fullscreen-views-android-only"},"Blocking fullscreen views (Android only)"),(0,a.mdx)("p",null,"There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary."),(0,a.mdx)("p",null,"Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"view.setTag(R.id.flipper_skip_view_traversal, true);\n")),(0,a.mdx)("h3",{id:"blocking-empty-view-groups-android-only"},"Blocking empty view groups (Android only)"),(0,a.mdx)("p",null,"If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment."),(0,a.mdx)("p",null,"Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker."),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);\n")),(0,a.mdx)("h2",{id:"ios"},"iOS"),(0,a.mdx)("h3",{id:"standard-uiview-only"},"Standard UIView Only"),(0,a.mdx)("p",null,"To debug layout using Flipper, add the following pod:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Once you have added the pod, initialise the plugin and add it to the ",(0,a.mdx)("inlineCode",{parentName:"p"},"FlipperClient")," as follows."),(0,a.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\nSKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];\n"))),(0,a.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))),(0,a.mdx)("h3",{id:"with-componentkit-support"},"With ComponentKit Support"),(0,a.mdx)("p",null,"If you want to enable ",(0,a.mdx)("a",{parentName:"p",href:"https://github.com/facebook/componentkit"},"ComponentKit support")," in the Layout Inspector, you need to add ",(0,a.mdx)("inlineCode",{parentName:"p"},"FlipperKit/FlipperKitLayoutComponentKitSupport")," to your Podfile:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below."),(0,a.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n#import \n\nSKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];\n[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application\n withDescriptorMapper: layoutDescriptorMapper]];\n"))),(0,a.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nFlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))))}v.isMDXComponent=!0},85162:(e,t,n)=>{n.r(t),n.d(t,{default:()=>o});var r=n(67294),i=n(86010);const a="tabItem_Ymn6";function o(e){var t=e.children,n=e.hidden,o=e.className;return r.createElement("div",{role:"tabpanel",className:(0,i.default)(a,o),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>k});var r=n(83117),i=n(67294),a=n(86010),o=n(12466),l=n(76775),p=n(91980),u=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=i.Children.map(e,(function(e){if(!e||(0,i.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,i.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,u.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function m(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function f(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId,a=(0,l.k6)(),o=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:r});return[(0,p._X)(o),(0,i.useCallback)((function(e){if(o){var t=new URLSearchParams(a.location.search);t.set(o,e),a.replace(Object.assign({},a.location,{search:t.toString()}))}}),[o,a])]}function h(e){var t,n,r,a,o=e.defaultValue,l=e.queryString,p=void 0!==l&&l,u=e.groupId,c=d(e),h=(0,i.useState)((function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var i=null!=(t=r.find((function(e){return e.default})))?t:r[0];if(!i)throw new Error("Unexpected error: 0 tabValues");return i.value}({defaultValue:o,tabValues:c})})),v=h[0],g=h[1],y=f({queryString:p,groupId:u}),b=y[0],w=y[1],x=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:u}.groupId),n=(0,s.Nk)(t),r=n[0],a=n[1],[r,(0,i.useCallback)((function(e){t&&a.set(e)}),[t,a])]),k=x[0],D=x[1],N=function(){var e=null!=b?b:k;return m({value:e,tabValues:c})?e:null}();return(0,i.useLayoutEffect)((function(){N&&g(N)}),[N]),{selectedValue:v,selectValue:(0,i.useCallback)((function(e){if(!m({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);g(e),w(e),D(e)}),[w,D,c]),tabValues:c}}var v=n(72389);const g="tabList__CuJ",y="tabItem_LNqP";function b(e){var t=e.className,n=e.block,l=e.selectedValue,p=e.selectValue,u=e.tabValues,s=[],c=(0,o.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),r=u[n].value;r!==l&&(c(t),p(r))},m=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var r,i=s.indexOf(e.currentTarget)+1;n=null!=(r=s[i])?r:s[0];break;case"ArrowLeft":var a,o=s.indexOf(e.currentTarget)-1;n=null!=(a=s[o])?a:s[s.length-1]}null==(t=n)||t.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.default)("tabs",{"tabs--block":n},t)},u.map((function(e){var t=e.value,n=e.label,o=e.attributes;return i.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:function(e){return s.push(e)},onKeyDown:m,onClick:d},o,{className:(0,a.default)("tabs__item",y,null==o?void 0:o.className,{"tabs__item--active":l===t})}),null!=n?n:t)})))}function w(e){var t=e.lazy,n=e.children,r=e.selectedValue,a=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var o=a.find((function(e){return e.props.value===r}));return o?(0,i.cloneElement)(o,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},a.map((function(e,t){return(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==r})})))}function x(e){var t=h(e);return i.createElement("div",{className:(0,a.default)("tabs-container",g)},i.createElement(b,(0,r.Z)({},e,t)),i.createElement(w,(0,r.Z)({},e,t)))}function k(e){var t=(0,v.default)();return i.createElement(x,(0,r.Z)({key:String(t)},e))}}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6033],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>u,MDXProvider:()=>d,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>s});var r=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=r.createContext({}),s=function(e){return function(t){var n=c(t.components);return r.createElement(e,a({},t,{components:n}))}},c=function(e){var t=r.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return r.createElement(u.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,o=e.parentName,u=p(e,["components","mdxType","originalType","parentName"]),s=c(n),d=i,f=s["".concat(o,".").concat(d)]||s[d]||m[d]||a;return n?r.createElement(f,l(l({ref:t},u),{},{components:n})):r.createElement(f,l({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:i,o[1]=l;for(var u=2;u{n.r(t),n.d(t,{contentTitle:()=>d,default:()=>v,frontMatter:()=>c,metadata:()=>m,toc:()=>f});var r=n(83117),i=n(80102),a=(n(67294),n(3905)),o=n(44996),l=n(39960),p=n(74866),u=n(85162),s=["components"],c={},d=void 0,m={type:"mdx",permalink:"/docs/plugins/inspector/setup",source:"@site/src/embedded-pages/docs/plugins/inspector/setup.mdx",description:"To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},f=[{value:"Android",id:"android",level:2},{value:"Standard Android view only",id:"standard-android-view-only",level:3},{value:"With Litho Support",id:"with-litho-support",level:3},{value:"Blocking fullscreen views (Android only)",id:"blocking-fullscreen-views-android-only",level:3},{value:"Blocking empty view groups (Android only)",id:"blocking-empty-view-groups-android-only",level:3},{value:"iOS",id:"ios",level:2},{value:"Standard UIView Only",id:"standard-uiview-only",level:3},{value:"With ComponentKit Support",id:"with-componentkit-support",level:3}],h={toc:f};function v(e){var t=e.components,n=(0,i.Z)(e,s);return(0,a.mdx)("wrapper",(0,r.Z)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"To use the ",(0,a.mdx)(l.default,{to:(0,o.default)("/docs/features/plugins/inspector"),mdxType:"Link"},"Layout Inspector plugin"),", you need to add the plugin to your Flipper client instance."),(0,a.mdx)("h2",{id:"android"},"Android"),(0,a.mdx)("h3",{id:"standard-android-view-only"},"Standard Android view only"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,a.mdx)("h3",{id:"with-litho-support"},"With Litho Support"),(0,a.mdx)("p",null,"Litho support is provided via an optional plugin."),(0,a.mdx)("p",null,"You also need to compile in the ",(0,a.mdx)("inlineCode",{parentName:"p"},"litho-annotations")," package, as Flipper reflects on them at runtime. So ensure to not just include them as ",(0,a.mdx)("inlineCode",{parentName:"p"},"compileOnly")," in your gradle configuration:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.260.0'\n debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'\n // ...\n}\n")),(0,a.mdx)("p",null,"If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies."),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.litho.config.ComponentsConfiguration;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\nimport com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;\n\n// Instead of hard-coding this setting, it's a good practice to tie\n// this to a BuildConfig flag, that you only enable for debug builds\n// of your application.\nComponentsConfiguration.isDebugModeEnabled = true;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n// This adds Litho capabilities to the layout inspector.\nLithoFlipperDescriptors.add(descriptorMapping);\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,a.mdx)("h3",{id:"blocking-fullscreen-views-android-only"},"Blocking fullscreen views (Android only)"),(0,a.mdx)("p",null,"There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary."),(0,a.mdx)("p",null,"Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"view.setTag(R.id.flipper_skip_view_traversal, true);\n")),(0,a.mdx)("h3",{id:"blocking-empty-view-groups-android-only"},"Blocking empty view groups (Android only)"),(0,a.mdx)("p",null,"If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment."),(0,a.mdx)("p",null,"Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker."),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);\n")),(0,a.mdx)("h2",{id:"ios"},"iOS"),(0,a.mdx)("h3",{id:"standard-uiview-only"},"Standard UIView Only"),(0,a.mdx)("p",null,"To debug layout using Flipper, add the following pod:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Once you have added the pod, initialise the plugin and add it to the ",(0,a.mdx)("inlineCode",{parentName:"p"},"FlipperClient")," as follows."),(0,a.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\nSKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];\n"))),(0,a.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))),(0,a.mdx)("h3",{id:"with-componentkit-support"},"With ComponentKit Support"),(0,a.mdx)("p",null,"If you want to enable ",(0,a.mdx)("a",{parentName:"p",href:"https://github.com/facebook/componentkit"},"ComponentKit support")," in the Layout Inspector, you need to add ",(0,a.mdx)("inlineCode",{parentName:"p"},"FlipperKit/FlipperKitLayoutComponentKitSupport")," to your Podfile:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below."),(0,a.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n#import \n\nSKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];\n[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application\n withDescriptorMapper: layoutDescriptorMapper]];\n"))),(0,a.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nFlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))))}v.isMDXComponent=!0},85162:(e,t,n)=>{n.r(t),n.d(t,{default:()=>o});var r=n(67294),i=n(86010);const a="tabItem_Ymn6";function o(e){var t=e.children,n=e.hidden,o=e.className;return r.createElement("div",{role:"tabpanel",className:(0,i.default)(a,o),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>k});var r=n(83117),i=n(67294),a=n(86010),o=n(12466),l=n(76775),p=n(91980),u=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=i.Children.map(e,(function(e){if(!e||(0,i.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,i.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,u.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function m(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function f(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId,a=(0,l.k6)(),o=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:r});return[(0,p._X)(o),(0,i.useCallback)((function(e){if(o){var t=new URLSearchParams(a.location.search);t.set(o,e),a.replace(Object.assign({},a.location,{search:t.toString()}))}}),[o,a])]}function h(e){var t,n,r,a,o=e.defaultValue,l=e.queryString,p=void 0!==l&&l,u=e.groupId,c=d(e),h=(0,i.useState)((function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var i=null!=(t=r.find((function(e){return e.default})))?t:r[0];if(!i)throw new Error("Unexpected error: 0 tabValues");return i.value}({defaultValue:o,tabValues:c})})),v=h[0],g=h[1],y=f({queryString:p,groupId:u}),b=y[0],w=y[1],x=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:u}.groupId),n=(0,s.Nk)(t),r=n[0],a=n[1],[r,(0,i.useCallback)((function(e){t&&a.set(e)}),[t,a])]),k=x[0],D=x[1],N=function(){var e=null!=b?b:k;return m({value:e,tabValues:c})?e:null}();return(0,i.useLayoutEffect)((function(){N&&g(N)}),[N]),{selectedValue:v,selectValue:(0,i.useCallback)((function(e){if(!m({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);g(e),w(e),D(e)}),[w,D,c]),tabValues:c}}var v=n(72389);const g="tabList__CuJ",y="tabItem_LNqP";function b(e){var t=e.className,n=e.block,l=e.selectedValue,p=e.selectValue,u=e.tabValues,s=[],c=(0,o.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),r=u[n].value;r!==l&&(c(t),p(r))},m=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var r,i=s.indexOf(e.currentTarget)+1;n=null!=(r=s[i])?r:s[0];break;case"ArrowLeft":var a,o=s.indexOf(e.currentTarget)-1;n=null!=(a=s[o])?a:s[s.length-1]}null==(t=n)||t.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.default)("tabs",{"tabs--block":n},t)},u.map((function(e){var t=e.value,n=e.label,o=e.attributes;return i.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:function(e){return s.push(e)},onKeyDown:m,onClick:d},o,{className:(0,a.default)("tabs__item",y,null==o?void 0:o.className,{"tabs__item--active":l===t})}),null!=n?n:t)})))}function w(e){var t=e.lazy,n=e.children,r=e.selectedValue,a=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var o=a.find((function(e){return e.props.value===r}));return o?(0,i.cloneElement)(o,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},a.map((function(e,t){return(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==r})})))}function x(e){var t=h(e);return i.createElement("div",{className:(0,a.default)("tabs-container",g)},i.createElement(b,(0,r.Z)({},e,t)),i.createElement(w,(0,r.Z)({},e,t)))}function k(e){var t=(0,v.default)();return i.createElement(x,(0,r.Z)({key:String(t)},e))}}}]); \ No newline at end of file diff --git a/assets/js/3a952f51.620f906d.js b/assets/js/3a952f51.c7de4763.js similarity index 98% rename from assets/js/3a952f51.620f906d.js rename to assets/js/3a952f51.c7de4763.js index 904c0f83605..9265111ed31 100644 --- a/assets/js/3a952f51.620f906d.js +++ b/assets/js/3a952f51.c7de4763.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[428],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>c,MDXProvider:()=>d,mdx:()=>y,useMDXComponents:()=>s,withMDXComponents:()=>u});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(){return o=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var c=r.createContext({}),u=function(e){return function(n){var t=s(n.components);return r.createElement(e,o({},n,{components:t}))}},s=function(e){var n=r.useContext(c),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=s(e.components);return r.createElement(c.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,i=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),u=s(t),d=a,f=u["".concat(i,".").concat(d)]||u[d]||m[d]||o;return t?r.createElement(f,l(l({ref:n},c),{},{components:t})):r.createElement(f,l({ref:n},c))}));function y(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{contentTitle:()=>u,default:()=>f,frontMatter:()=>c,metadata:()=>s,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(44996),l=t(39960),p=["components"],c={},u=void 0,s={type:"mdx",permalink:"/docs/plugins/leak-canary/setup",source:"@site/src/embedded-pages/docs/plugins/leak-canary/setup.mdx",description:"To setup the LeakCanary plugin, take the following steps:",frontMatter:{}},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,p);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To setup the ",(0,o.mdx)(l.default,{to:(0,i.default)("/docs/features/plugins/leak-canary"),mdxType:"Link"},"LeakCanary plugin"),", take the following steps:"),(0,o.mdx)("ol",null,(0,o.mdx)("li",{parentName:"ol"},"Ensure that you have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"li"},"build.gradle")," including the plugin dependency, such as is shown in the following snippet:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.259.0'\n debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'\n}\n")),(0,o.mdx)("ol",{start:2},(0,o.mdx)("li",{parentName:"ol"},"Update your the ",(0,o.mdx)("inlineCode",{parentName:"li"},"onCreate")," method in you ",(0,o.mdx)("inlineCode",{parentName:"li"},"Application")," to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kt"},"import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener\nimport com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin\n\n...\n\n override fun onCreate() {\n super.onCreate()\n\n /*\n set the flipper listener in leak canary config\n */\n LeakCanary.config = LeakCanary.config.run {\n copy(eventListeners = eventListeners + FlipperLeakEventListener())\n }\n\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n /*\n add leak canary plugin to flipper\n */\n client.addPlugin(LeakCanary2FlipperPlugin())\n client.start()\n }\n }\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[428],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>c,MDXProvider:()=>d,mdx:()=>y,useMDXComponents:()=>s,withMDXComponents:()=>u});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(){return o=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var c=r.createContext({}),u=function(e){return function(n){var t=s(n.components);return r.createElement(e,o({},n,{components:t}))}},s=function(e){var n=r.useContext(c),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=s(e.components);return r.createElement(c.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,i=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),u=s(t),d=a,f=u["".concat(i,".").concat(d)]||u[d]||m[d]||o;return t?r.createElement(f,l(l({ref:n},c),{},{components:t})):r.createElement(f,l({ref:n},c))}));function y(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{contentTitle:()=>u,default:()=>f,frontMatter:()=>c,metadata:()=>s,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(44996),l=t(39960),p=["components"],c={},u=void 0,s={type:"mdx",permalink:"/docs/plugins/leak-canary/setup",source:"@site/src/embedded-pages/docs/plugins/leak-canary/setup.mdx",description:"To setup the LeakCanary plugin, take the following steps:",frontMatter:{}},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,p);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To setup the ",(0,o.mdx)(l.default,{to:(0,i.default)("/docs/features/plugins/leak-canary"),mdxType:"Link"},"LeakCanary plugin"),", take the following steps:"),(0,o.mdx)("ol",null,(0,o.mdx)("li",{parentName:"ol"},"Ensure that you have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"li"},"build.gradle")," including the plugin dependency, such as is shown in the following snippet:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.260.0'\n debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'\n}\n")),(0,o.mdx)("ol",{start:2},(0,o.mdx)("li",{parentName:"ol"},"Update your the ",(0,o.mdx)("inlineCode",{parentName:"li"},"onCreate")," method in you ",(0,o.mdx)("inlineCode",{parentName:"li"},"Application")," to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kt"},"import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener\nimport com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin\n\n...\n\n override fun onCreate() {\n super.onCreate()\n\n /*\n set the flipper listener in leak canary config\n */\n LeakCanary.config = LeakCanary.config.run {\n copy(eventListeners = eventListeners + FlipperLeakEventListener())\n }\n\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n /*\n add leak canary plugin to flipper\n */\n client.addPlugin(LeakCanary2FlipperPlugin())\n client.start()\n }\n }\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3f3d2568.203d5bc1.js b/assets/js/3f3d2568.ef89c82f.js similarity index 99% rename from assets/js/3f3d2568.203d5bc1.js rename to assets/js/3f3d2568.ef89c82f.js index cf9b6d42901..7383483cc4e 100644 --- a/assets/js/3f3d2568.203d5bc1.js +++ b/assets/js/3f3d2568.ef89c82f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3541,1339],{3905:(e,t,r)=>{r.r(t),r.d(t,{MDXContext:()=>p,MDXProvider:()=>d,mdx:()=>b,useMDXComponents:()=>c,withMDXComponents:()=>s});var n=r(67294);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var p=n.createContext({}),s=function(e){return function(t){var r=c(t.components);return n.createElement(e,a({},t,{components:r}))}},c=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},d=function(e){var t=c(e.components);return n.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,a=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),s=c(r),d=o,m=s["".concat(l,".").concat(d)]||s[d]||f[d]||a;return r?n.createElement(m,i(i({ref:t},p),{},{components:r})):n.createElement(m,i({ref:t},p))}));function b(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=r.length,l=new Array(a);l[0]=m;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i.mdxType="string"==typeof e?e:o,l[1]=i;for(var p=2;p{r.r(t),r.d(t,{contentTitle:()=>d,default:()=>g,frontMatter:()=>c,metadata:()=>f,toc:()=>m});var n=r(83117),o=r(80102),a=(r(67294),r(3905)),l=r(44996),i=r(39960),u=r(74866),p=r(85162),s=["components"],c={},d=void 0,f={type:"mdx",permalink:"/docs/plugins/network/setup",source:"@site/src/embedded-pages/docs/plugins/network/setup.mdx",description:"To use the Network plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},m=[{value:"Android",id:"android",level:2},{value:"OkHttp Integration",id:"okhttp-integration",level:3},{value:"Protobuf / Retrofit Integration",id:"protobuf--retrofit-integration",level:3},{value:"iOS",id:"ios",level:2},{value:"Protobuf + Retrofit Setup",id:"protobuf--retrofit-setup",level:2},{value:"Gradle Dependencies",id:"gradle-dependencies",level:3},{value:"Sending Retrofit Service",id:"sending-retrofit-service",level:3}],b={toc:m};function g(e){var t=e.components,r=(0,o.Z)(e,s);return(0,a.mdx)("wrapper",(0,n.Z)({},b,r,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"To use the ",(0,a.mdx)(i.default,{to:(0,l.default)("/docs/features/plugins/network"),mdxType:"Link"},"Network plugin"),", you need to add the plugin to your Flipper client instance."),(0,a.mdx)("h2",{id:"android"},"Android"),(0,a.mdx)("p",null,"The network plugin is shipped as a separate Maven artifact, as follows:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.259.0'\n}\n")),(0,a.mdx)("p",null,"Once added to your dependencies, you can instantiate the plugin and add it to the client:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;\n\nNetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();\nnew NetworkingModule.CustomClientBuilder() {\n @Override\n public void apply(OkHttpClient.Builder builder) {\n builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));\n }\n});\nclient.addPlugin(networkFlipperPlugin);\n")),(0,a.mdx)("h3",{id:"okhttp-integration"},"OkHttp Integration"),(0,a.mdx)("p",null,"If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;\n\nnew OkHttpClient.Builder()\n .addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))\n .build();\n")),(0,a.mdx)("p",null,"As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic."),(0,a.mdx)("h3",{id:"protobuf--retrofit-integration"},"Protobuf / Retrofit Integration"),(0,a.mdx)("p",null,"If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'\n}\n")),(0,a.mdx)("p",null,"Then call ",(0,a.mdx)("inlineCode",{parentName:"p"},"SendProtobufToFlipperFromRetrofit")," for each service class:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-kotlin"},'import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n\nSendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)\n')),(0,a.mdx)("h2",{id:"ios"},"iOS"),(0,a.mdx)("p",null,"To enable network inspection, add the following pod to your Podfile:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Initialize the plugin in the following way by updating AppDelegate.m:"),(0,a.mdx)(u.default,{defaultValue:"objc",values:[{label:"ObjC",value:"objc"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(p.default,{value:"objc",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\n[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n"))),(0,a.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nclient?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n")))),(0,a.mdx)("h2",{id:"protobuf--retrofit-setup"},"Protobuf + Retrofit Setup"),(0,a.mdx)("h3",{id:"gradle-dependencies"},"Gradle Dependencies"),(0,a.mdx)("p",null,"Ensure that you already have an explicit dependency in your application's ",(0,a.mdx)("inlineCode",{parentName:"p"},"build.gradle")," including the plugin dependency, as shown in the following example:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},'dependencies {\n implementation "com.squareup.retrofit2:retrofit:2.9.0"\n implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"\n\n // update version below to match latest Flipper client app\n debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"\n}\n')),(0,a.mdx)("h3",{id:"sending-retrofit-service"},"Sending Retrofit Service"),(0,a.mdx)("p",null,"If you have a Retrofit service interface ",(0,a.mdx)("inlineCode",{parentName:"p"},"PersonService")," which has Protobuf body or return types then at the time you create your implementation, call the plugin with your ",(0,a.mdx)("inlineCode",{parentName:"p"},"baseURL")," and service class, as follows:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n...\nval personService = retrofit.create(PersonService::class.java)\nSendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)\n")))}g.isMDXComponent=!0},85162:(e,t,r)=>{r.r(t),r.d(t,{default:()=>l});var n=r(67294),o=r(86010);const a="tabItem_Ymn6";function l(e){var t=e.children,r=e.hidden,l=e.className;return n.createElement("div",{role:"tabpanel",className:(0,o.default)(a,l),hidden:r},t)}},74866:(e,t,r)=>{r.r(t),r.d(t,{default:()=>x});var n=r(83117),o=r(67294),a=r(86010),l=r(12466),i=r(76775),u=r(91980),p=r(67392),s=r(50012);function c(e){return function(e){var t,r;return null!=(t=null==(r=o.Children.map(e,(function(e){if(!e||(0,o.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:r.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,r=e.children;return(0,o.useMemo)((function(){var e=null!=t?t:c(r);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,r])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId,a=(0,i.k6)(),l=function(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId;if("string"==typeof r)return r;if(!1===r)return null;if(!0===r&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=n?n:null}({queryString:r,groupId:n});return[(0,u._X)(l),(0,o.useCallback)((function(e){if(l){var t=new URLSearchParams(a.location.search);t.set(l,e),a.replace(Object.assign({},a.location,{search:t.toString()}))}}),[l,a])]}function b(e){var t,r,n,a,l=e.defaultValue,i=e.queryString,u=void 0!==i&&i,p=e.groupId,c=d(e),b=(0,o.useState)((function(){return function(e){var t,r=e.defaultValue,n=e.tabValues;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(r){if(!f({value:r,tabValues:n}))throw new Error('Docusaurus error: The has a defaultValue "'+r+'" but none of its children has the corresponding value. Available values are: '+n.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return r}var o=null!=(t=n.find((function(e){return e.default})))?t:n[0];if(!o)throw new Error("Unexpected error: 0 tabValues");return o.value}({defaultValue:l,tabValues:c})})),g=b[0],v=b[1],h=m({queryString:u,groupId:p}),y=h[0],w=h[1],k=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),r=(0,s.Nk)(t),n=r[0],a=r[1],[n,(0,o.useCallback)((function(e){t&&a.set(e)}),[t,a])]),x=k[0],N=k[1],P=function(){var e=null!=y?y:x;return f({value:e,tabValues:c})?e:null}();return(0,o.useLayoutEffect)((function(){P&&v(P)}),[P]),{selectedValue:g,selectValue:(0,o.useCallback)((function(e){if(!f({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);v(e),w(e),N(e)}),[w,N,c]),tabValues:c}}var g=r(72389);const v="tabList__CuJ",h="tabItem_LNqP";function y(e){var t=e.className,r=e.block,i=e.selectedValue,u=e.selectValue,p=e.tabValues,s=[],c=(0,l.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,r=s.indexOf(t),n=p[r].value;n!==i&&(c(t),u(n))},f=function(e){var t,r=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var n,o=s.indexOf(e.currentTarget)+1;r=null!=(n=s[o])?n:s[0];break;case"ArrowLeft":var a,l=s.indexOf(e.currentTarget)-1;r=null!=(a=s[l])?a:s[s.length-1]}null==(t=r)||t.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.default)("tabs",{"tabs--block":r},t)},p.map((function(e){var t=e.value,r=e.label,l=e.attributes;return o.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:function(e){return s.push(e)},onKeyDown:f,onClick:d},l,{className:(0,a.default)("tabs__item",h,null==l?void 0:l.className,{"tabs__item--active":i===t})}),null!=r?r:t)})))}function w(e){var t=e.lazy,r=e.children,n=e.selectedValue,a=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){var l=a.find((function(e){return e.props.value===n}));return l?(0,o.cloneElement)(l,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},a.map((function(e,t){return(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==n})})))}function k(e){var t=b(e);return o.createElement("div",{className:(0,a.default)("tabs-container",v)},o.createElement(y,(0,n.Z)({},e,t)),o.createElement(w,(0,n.Z)({},e,t)))}function x(e){var t=(0,g.default)();return o.createElement(k,(0,n.Z)({key:String(t)},e))}},98945:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>m,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=r(83117),o=r(80102),a=(r(67294),r(3905)),l=r(16695),i=["components"],u={id:"network",title:"Network Plugin Setup",sidebar_label:"Network",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx"},p=void 0,s={unversionedId:"setup/plugins/network",id:"setup/plugins/network",title:"Network Plugin Setup",description:"",source:"@site/../docs/setup/plugins/network.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/network",permalink:"/docs/setup/plugins/network",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"network",title:"Network Plugin Setup",sidebar_label:"Network",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx"},sidebar:"main",previous:{title:"Navigation",permalink:"/docs/setup/plugins/navigation"},next:{title:"Shared Preferences Viewer",permalink:"/docs/setup/plugins/preferences"}},c={},d=[],f={toc:d};function m(e){var t=e.components,r=(0,o.Z)(e,i);return(0,a.mdx)("wrapper",(0,n.Z)({},f,r,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)(l.default,{mdxType:"Article"}))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3541,1339],{3905:(e,t,r)=>{r.r(t),r.d(t,{MDXContext:()=>p,MDXProvider:()=>d,mdx:()=>b,useMDXComponents:()=>c,withMDXComponents:()=>s});var n=r(67294);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var p=n.createContext({}),s=function(e){return function(t){var r=c(t.components);return n.createElement(e,a({},t,{components:r}))}},c=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},d=function(e){var t=c(e.components);return n.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,o=e.mdxType,a=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),s=c(r),d=o,m=s["".concat(l,".").concat(d)]||s[d]||f[d]||a;return r?n.createElement(m,i(i({ref:t},p),{},{components:r})):n.createElement(m,i({ref:t},p))}));function b(e,t){var r=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=r.length,l=new Array(a);l[0]=m;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i.mdxType="string"==typeof e?e:o,l[1]=i;for(var p=2;p{r.r(t),r.d(t,{contentTitle:()=>d,default:()=>g,frontMatter:()=>c,metadata:()=>f,toc:()=>m});var n=r(83117),o=r(80102),a=(r(67294),r(3905)),l=r(44996),i=r(39960),u=r(74866),p=r(85162),s=["components"],c={},d=void 0,f={type:"mdx",permalink:"/docs/plugins/network/setup",source:"@site/src/embedded-pages/docs/plugins/network/setup.mdx",description:"To use the Network plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},m=[{value:"Android",id:"android",level:2},{value:"OkHttp Integration",id:"okhttp-integration",level:3},{value:"Protobuf / Retrofit Integration",id:"protobuf--retrofit-integration",level:3},{value:"iOS",id:"ios",level:2},{value:"Protobuf + Retrofit Setup",id:"protobuf--retrofit-setup",level:2},{value:"Gradle Dependencies",id:"gradle-dependencies",level:3},{value:"Sending Retrofit Service",id:"sending-retrofit-service",level:3}],b={toc:m};function g(e){var t=e.components,r=(0,o.Z)(e,s);return(0,a.mdx)("wrapper",(0,n.Z)({},b,r,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"To use the ",(0,a.mdx)(i.default,{to:(0,l.default)("/docs/features/plugins/network"),mdxType:"Link"},"Network plugin"),", you need to add the plugin to your Flipper client instance."),(0,a.mdx)("h2",{id:"android"},"Android"),(0,a.mdx)("p",null,"The network plugin is shipped as a separate Maven artifact, as follows:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.260.0'\n}\n")),(0,a.mdx)("p",null,"Once added to your dependencies, you can instantiate the plugin and add it to the client:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;\n\nNetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();\nnew NetworkingModule.CustomClientBuilder() {\n @Override\n public void apply(OkHttpClient.Builder builder) {\n builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));\n }\n});\nclient.addPlugin(networkFlipperPlugin);\n")),(0,a.mdx)("h3",{id:"okhttp-integration"},"OkHttp Integration"),(0,a.mdx)("p",null,"If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;\n\nnew OkHttpClient.Builder()\n .addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))\n .build();\n")),(0,a.mdx)("p",null,"As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic."),(0,a.mdx)("h3",{id:"protobuf--retrofit-integration"},"Protobuf / Retrofit Integration"),(0,a.mdx)("p",null,"If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'\n}\n")),(0,a.mdx)("p",null,"Then call ",(0,a.mdx)("inlineCode",{parentName:"p"},"SendProtobufToFlipperFromRetrofit")," for each service class:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-kotlin"},'import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n\nSendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)\n')),(0,a.mdx)("h2",{id:"ios"},"iOS"),(0,a.mdx)("p",null,"To enable network inspection, add the following pod to your Podfile:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version\n")),(0,a.mdx)("p",null,"Initialize the plugin in the following way by updating AppDelegate.m:"),(0,a.mdx)(u.default,{defaultValue:"objc",values:[{label:"ObjC",value:"objc"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,a.mdx)(p.default,{value:"objc",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\n[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n"))),(0,a.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nclient?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n")))),(0,a.mdx)("h2",{id:"protobuf--retrofit-setup"},"Protobuf + Retrofit Setup"),(0,a.mdx)("h3",{id:"gradle-dependencies"},"Gradle Dependencies"),(0,a.mdx)("p",null,"Ensure that you already have an explicit dependency in your application's ",(0,a.mdx)("inlineCode",{parentName:"p"},"build.gradle")," including the plugin dependency, as shown in the following example:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre",className:"language-groovy"},'dependencies {\n implementation "com.squareup.retrofit2:retrofit:2.9.0"\n implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"\n\n // update version below to match latest Flipper client app\n debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"\n}\n')),(0,a.mdx)("h3",{id:"sending-retrofit-service"},"Sending Retrofit Service"),(0,a.mdx)("p",null,"If you have a Retrofit service interface ",(0,a.mdx)("inlineCode",{parentName:"p"},"PersonService")," which has Protobuf body or return types then at the time you create your implementation, call the plugin with your ",(0,a.mdx)("inlineCode",{parentName:"p"},"baseURL")," and service class, as follows:"),(0,a.mdx)("pre",null,(0,a.mdx)("code",{parentName:"pre"},"import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n...\nval personService = retrofit.create(PersonService::class.java)\nSendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)\n")))}g.isMDXComponent=!0},85162:(e,t,r)=>{r.r(t),r.d(t,{default:()=>l});var n=r(67294),o=r(86010);const a="tabItem_Ymn6";function l(e){var t=e.children,r=e.hidden,l=e.className;return n.createElement("div",{role:"tabpanel",className:(0,o.default)(a,l),hidden:r},t)}},74866:(e,t,r)=>{r.r(t),r.d(t,{default:()=>x});var n=r(83117),o=r(67294),a=r(86010),l=r(12466),i=r(76775),u=r(91980),p=r(67392),s=r(50012);function c(e){return function(e){var t,r;return null!=(t=null==(r=o.Children.map(e,(function(e){if(!e||(0,o.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:r.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,r=e.children;return(0,o.useMemo)((function(){var e=null!=t?t:c(r);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,r])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId,a=(0,i.k6)(),l=function(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId;if("string"==typeof r)return r;if(!1===r)return null;if(!0===r&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=n?n:null}({queryString:r,groupId:n});return[(0,u._X)(l),(0,o.useCallback)((function(e){if(l){var t=new URLSearchParams(a.location.search);t.set(l,e),a.replace(Object.assign({},a.location,{search:t.toString()}))}}),[l,a])]}function b(e){var t,r,n,a,l=e.defaultValue,i=e.queryString,u=void 0!==i&&i,p=e.groupId,c=d(e),b=(0,o.useState)((function(){return function(e){var t,r=e.defaultValue,n=e.tabValues;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(r){if(!f({value:r,tabValues:n}))throw new Error('Docusaurus error: The has a defaultValue "'+r+'" but none of its children has the corresponding value. Available values are: '+n.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return r}var o=null!=(t=n.find((function(e){return e.default})))?t:n[0];if(!o)throw new Error("Unexpected error: 0 tabValues");return o.value}({defaultValue:l,tabValues:c})})),g=b[0],v=b[1],h=m({queryString:u,groupId:p}),y=h[0],w=h[1],k=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),r=(0,s.Nk)(t),n=r[0],a=r[1],[n,(0,o.useCallback)((function(e){t&&a.set(e)}),[t,a])]),x=k[0],N=k[1],P=function(){var e=null!=y?y:x;return f({value:e,tabValues:c})?e:null}();return(0,o.useLayoutEffect)((function(){P&&v(P)}),[P]),{selectedValue:g,selectValue:(0,o.useCallback)((function(e){if(!f({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);v(e),w(e),N(e)}),[w,N,c]),tabValues:c}}var g=r(72389);const v="tabList__CuJ",h="tabItem_LNqP";function y(e){var t=e.className,r=e.block,i=e.selectedValue,u=e.selectValue,p=e.tabValues,s=[],c=(0,l.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,r=s.indexOf(t),n=p[r].value;n!==i&&(c(t),u(n))},f=function(e){var t,r=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var n,o=s.indexOf(e.currentTarget)+1;r=null!=(n=s[o])?n:s[0];break;case"ArrowLeft":var a,l=s.indexOf(e.currentTarget)-1;r=null!=(a=s[l])?a:s[s.length-1]}null==(t=r)||t.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.default)("tabs",{"tabs--block":r},t)},p.map((function(e){var t=e.value,r=e.label,l=e.attributes;return o.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:function(e){return s.push(e)},onKeyDown:f,onClick:d},l,{className:(0,a.default)("tabs__item",h,null==l?void 0:l.className,{"tabs__item--active":i===t})}),null!=r?r:t)})))}function w(e){var t=e.lazy,r=e.children,n=e.selectedValue,a=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){var l=a.find((function(e){return e.props.value===n}));return l?(0,o.cloneElement)(l,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},a.map((function(e,t){return(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==n})})))}function k(e){var t=b(e);return o.createElement("div",{className:(0,a.default)("tabs-container",v)},o.createElement(y,(0,n.Z)({},e,t)),o.createElement(w,(0,n.Z)({},e,t)))}function x(e){var t=(0,g.default)();return o.createElement(k,(0,n.Z)({key:String(t)},e))}},98945:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>m,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=r(83117),o=r(80102),a=(r(67294),r(3905)),l=r(16695),i=["components"],u={id:"network",title:"Network Plugin Setup",sidebar_label:"Network",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx"},p=void 0,s={unversionedId:"setup/plugins/network",id:"setup/plugins/network",title:"Network Plugin Setup",description:"",source:"@site/../docs/setup/plugins/network.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/network",permalink:"/docs/setup/plugins/network",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"network",title:"Network Plugin Setup",sidebar_label:"Network",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/network/docs/setup.mdx"},sidebar:"main",previous:{title:"Navigation",permalink:"/docs/setup/plugins/navigation"},next:{title:"Shared Preferences Viewer",permalink:"/docs/setup/plugins/preferences"}},c={},d=[],f={toc:d};function m(e){var t=e.components,r=(0,o.Z)(e,i);return(0,a.mdx)("wrapper",(0,n.Z)({},f,r,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)(l.default,{mdxType:"Article"}))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4541de2f.46364c4f.js b/assets/js/4541de2f.3f777449.js similarity index 99% rename from assets/js/4541de2f.46364c4f.js rename to assets/js/4541de2f.3f777449.js index a86fb34b212..16a7f678e18 100644 --- a/assets/js/4541de2f.46364c4f.js +++ b/assets/js/4541de2f.3f777449.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1339],{3905:(e,t,r)=>{r.r(t),r.d(t,{MDXContext:()=>p,MDXProvider:()=>s,mdx:()=>v,useMDXComponents:()=>d,withMDXComponents:()=>c});var n=r(67294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(){return o=Object.assign||function(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var p=n.createContext({}),c=function(e){return function(t){var r=d(t.components);return n.createElement(e,o({},t,{components:r}))}},d=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},s=function(e){var t=d(e.components);return n.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),c=d(r),s=a,m=c["".concat(l,".").concat(s)]||c[s]||f[s]||o;return r?n.createElement(m,i(i({ref:t},p),{},{components:r})):n.createElement(m,i({ref:t},p))}));function v(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=m;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i.mdxType="string"==typeof e?e:a,l[1]=i;for(var p=2;p{r.r(t),r.d(t,{contentTitle:()=>s,default:()=>b,frontMatter:()=>d,metadata:()=>f,toc:()=>m});var n=r(83117),a=r(80102),o=(r(67294),r(3905)),l=r(44996),i=r(39960),u=r(74866),p=r(85162),c=["components"],d={},s=void 0,f={type:"mdx",permalink:"/docs/plugins/network/setup",source:"@site/src/embedded-pages/docs/plugins/network/setup.mdx",description:"To use the Network plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},m=[{value:"Android",id:"android",level:2},{value:"OkHttp Integration",id:"okhttp-integration",level:3},{value:"Protobuf / Retrofit Integration",id:"protobuf--retrofit-integration",level:3},{value:"iOS",id:"ios",level:2},{value:"Protobuf + Retrofit Setup",id:"protobuf--retrofit-setup",level:2},{value:"Gradle Dependencies",id:"gradle-dependencies",level:3},{value:"Sending Retrofit Service",id:"sending-retrofit-service",level:3}],v={toc:m};function b(e){var t=e.components,r=(0,a.Z)(e,c);return(0,o.mdx)("wrapper",(0,n.Z)({},v,r,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To use the ",(0,o.mdx)(i.default,{to:(0,l.default)("/docs/features/plugins/network"),mdxType:"Link"},"Network plugin"),", you need to add the plugin to your Flipper client instance."),(0,o.mdx)("h2",{id:"android"},"Android"),(0,o.mdx)("p",null,"The network plugin is shipped as a separate Maven artifact, as follows:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.259.0'\n}\n")),(0,o.mdx)("p",null,"Once added to your dependencies, you can instantiate the plugin and add it to the client:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;\n\nNetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();\nnew NetworkingModule.CustomClientBuilder() {\n @Override\n public void apply(OkHttpClient.Builder builder) {\n builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));\n }\n});\nclient.addPlugin(networkFlipperPlugin);\n")),(0,o.mdx)("h3",{id:"okhttp-integration"},"OkHttp Integration"),(0,o.mdx)("p",null,"If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;\n\nnew OkHttpClient.Builder()\n .addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))\n .build();\n")),(0,o.mdx)("p",null,"As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic."),(0,o.mdx)("h3",{id:"protobuf--retrofit-integration"},"Protobuf / Retrofit Integration"),(0,o.mdx)("p",null,"If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'\n}\n")),(0,o.mdx)("p",null,"Then call ",(0,o.mdx)("inlineCode",{parentName:"p"},"SendProtobufToFlipperFromRetrofit")," for each service class:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kotlin"},'import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n\nSendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)\n')),(0,o.mdx)("h2",{id:"ios"},"iOS"),(0,o.mdx)("p",null,"To enable network inspection, add the following pod to your Podfile:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Initialize the plugin in the following way by updating AppDelegate.m:"),(0,o.mdx)(u.default,{defaultValue:"objc",values:[{label:"ObjC",value:"objc"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(p.default,{value:"objc",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\n[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n"))),(0,o.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nclient?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n")))),(0,o.mdx)("h2",{id:"protobuf--retrofit-setup"},"Protobuf + Retrofit Setup"),(0,o.mdx)("h3",{id:"gradle-dependencies"},"Gradle Dependencies"),(0,o.mdx)("p",null,"Ensure that you already have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"p"},"build.gradle")," including the plugin dependency, as shown in the following example:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},'dependencies {\n implementation "com.squareup.retrofit2:retrofit:2.9.0"\n implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"\n\n // update version below to match latest Flipper client app\n debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"\n}\n')),(0,o.mdx)("h3",{id:"sending-retrofit-service"},"Sending Retrofit Service"),(0,o.mdx)("p",null,"If you have a Retrofit service interface ",(0,o.mdx)("inlineCode",{parentName:"p"},"PersonService")," which has Protobuf body or return types then at the time you create your implementation, call the plugin with your ",(0,o.mdx)("inlineCode",{parentName:"p"},"baseURL")," and service class, as follows:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre"},"import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n...\nval personService = retrofit.create(PersonService::class.java)\nSendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)\n")))}b.isMDXComponent=!0},85162:(e,t,r)=>{r.r(t),r.d(t,{default:()=>l});var n=r(67294),a=r(86010);const o="tabItem_Ymn6";function l(e){var t=e.children,r=e.hidden,l=e.className;return n.createElement("div",{role:"tabpanel",className:(0,a.default)(o,l),hidden:r},t)}},74866:(e,t,r)=>{r.r(t),r.d(t,{default:()=>x});var n=r(83117),a=r(67294),o=r(86010),l=r(12466),i=r(76775),u=r(91980),p=r(67392),c=r(50012);function d(e){return function(e){var t,r;return null!=(t=null==(r=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:r.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function s(e){var t=e.values,r=e.children;return(0,a.useMemo)((function(){var e=null!=t?t:d(r);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,r])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId,o=(0,i.k6)(),l=function(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId;if("string"==typeof r)return r;if(!1===r)return null;if(!0===r&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=n?n:null}({queryString:r,groupId:n});return[(0,u._X)(l),(0,a.useCallback)((function(e){if(l){var t=new URLSearchParams(o.location.search);t.set(l,e),o.replace(Object.assign({},o.location,{search:t.toString()}))}}),[l,o])]}function v(e){var t,r,n,o,l=e.defaultValue,i=e.queryString,u=void 0!==i&&i,p=e.groupId,d=s(e),v=(0,a.useState)((function(){return function(e){var t,r=e.defaultValue,n=e.tabValues;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(r){if(!f({value:r,tabValues:n}))throw new Error('Docusaurus error: The has a defaultValue "'+r+'" but none of its children has the corresponding value. Available values are: '+n.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return r}var a=null!=(t=n.find((function(e){return e.default})))?t:n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:l,tabValues:d})})),b=v[0],g=v[1],h=m({queryString:u,groupId:p}),y=h[0],w=h[1],k=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),r=(0,c.Nk)(t),n=r[0],o=r[1],[n,(0,a.useCallback)((function(e){t&&o.set(e)}),[t,o])]),x=k[0],N=k[1],O=function(){var e=null!=y?y:x;return f({value:e,tabValues:d})?e:null}();return(0,a.useLayoutEffect)((function(){O&&g(O)}),[O]),{selectedValue:b,selectValue:(0,a.useCallback)((function(e){if(!f({value:e,tabValues:d}))throw new Error("Can't select invalid tab value="+e);g(e),w(e),N(e)}),[w,N,d]),tabValues:d}}var b=r(72389);const g="tabList__CuJ",h="tabItem_LNqP";function y(e){var t=e.className,r=e.block,i=e.selectedValue,u=e.selectValue,p=e.tabValues,c=[],d=(0,l.o5)().blockElementScrollPositionUntilNextRender,s=function(e){var t=e.currentTarget,r=c.indexOf(t),n=p[r].value;n!==i&&(d(t),u(n))},f=function(e){var t,r=null;switch(e.key){case"Enter":s(e);break;case"ArrowRight":var n,a=c.indexOf(e.currentTarget)+1;r=null!=(n=c[a])?n:c[0];break;case"ArrowLeft":var o,l=c.indexOf(e.currentTarget)-1;r=null!=(o=c[l])?o:c[c.length-1]}null==(t=r)||t.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.default)("tabs",{"tabs--block":r},t)},p.map((function(e){var t=e.value,r=e.label,l=e.attributes;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:function(e){return c.push(e)},onKeyDown:f,onClick:s},l,{className:(0,o.default)("tabs__item",h,null==l?void 0:l.className,{"tabs__item--active":i===t})}),null!=r?r:t)})))}function w(e){var t=e.lazy,r=e.children,n=e.selectedValue,o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){var l=o.find((function(e){return e.props.value===n}));return l?(0,a.cloneElement)(l,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map((function(e,t){return(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n})})))}function k(e){var t=v(e);return a.createElement("div",{className:(0,o.default)("tabs-container",g)},a.createElement(y,(0,n.Z)({},e,t)),a.createElement(w,(0,n.Z)({},e,t)))}function x(e){var t=(0,b.default)();return a.createElement(k,(0,n.Z)({key:String(t)},e))}}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1339],{3905:(e,t,r)=>{r.r(t),r.d(t,{MDXContext:()=>p,MDXProvider:()=>s,mdx:()=>v,useMDXComponents:()=>d,withMDXComponents:()=>c});var n=r(67294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(){return o=Object.assign||function(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var p=n.createContext({}),c=function(e){return function(t){var r=d(t.components);return n.createElement(e,o({},t,{components:r}))}},d=function(e){var t=n.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},s=function(e){var t=d(e.components);return n.createElement(p.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),c=d(r),s=a,m=c["".concat(l,".").concat(s)]||c[s]||f[s]||o;return r?n.createElement(m,i(i({ref:t},p),{},{components:r})):n.createElement(m,i({ref:t},p))}));function v(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=m;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i.mdxType="string"==typeof e?e:a,l[1]=i;for(var p=2;p{r.r(t),r.d(t,{contentTitle:()=>s,default:()=>b,frontMatter:()=>d,metadata:()=>f,toc:()=>m});var n=r(83117),a=r(80102),o=(r(67294),r(3905)),l=r(44996),i=r(39960),u=r(74866),p=r(85162),c=["components"],d={},s=void 0,f={type:"mdx",permalink:"/docs/plugins/network/setup",source:"@site/src/embedded-pages/docs/plugins/network/setup.mdx",description:"To use the Network plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},m=[{value:"Android",id:"android",level:2},{value:"OkHttp Integration",id:"okhttp-integration",level:3},{value:"Protobuf / Retrofit Integration",id:"protobuf--retrofit-integration",level:3},{value:"iOS",id:"ios",level:2},{value:"Protobuf + Retrofit Setup",id:"protobuf--retrofit-setup",level:2},{value:"Gradle Dependencies",id:"gradle-dependencies",level:3},{value:"Sending Retrofit Service",id:"sending-retrofit-service",level:3}],v={toc:m};function b(e){var t=e.components,r=(0,a.Z)(e,c);return(0,o.mdx)("wrapper",(0,n.Z)({},v,r,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To use the ",(0,o.mdx)(i.default,{to:(0,l.default)("/docs/features/plugins/network"),mdxType:"Link"},"Network plugin"),", you need to add the plugin to your Flipper client instance."),(0,o.mdx)("h2",{id:"android"},"Android"),(0,o.mdx)("p",null,"The network plugin is shipped as a separate Maven artifact, as follows:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.260.0'\n}\n")),(0,o.mdx)("p",null,"Once added to your dependencies, you can instantiate the plugin and add it to the client:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;\n\nNetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();\nnew NetworkingModule.CustomClientBuilder() {\n @Override\n public void apply(OkHttpClient.Builder builder) {\n builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));\n }\n});\nclient.addPlugin(networkFlipperPlugin);\n")),(0,o.mdx)("h3",{id:"okhttp-integration"},"OkHttp Integration"),(0,o.mdx)("p",null,"If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;\n\nnew OkHttpClient.Builder()\n .addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))\n .build();\n")),(0,o.mdx)("p",null,"As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic."),(0,o.mdx)("h3",{id:"protobuf--retrofit-integration"},"Protobuf / Retrofit Integration"),(0,o.mdx)("p",null,"If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'\n}\n")),(0,o.mdx)("p",null,"Then call ",(0,o.mdx)("inlineCode",{parentName:"p"},"SendProtobufToFlipperFromRetrofit")," for each service class:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kotlin"},'import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n\nSendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)\n')),(0,o.mdx)("h2",{id:"ios"},"iOS"),(0,o.mdx)("p",null,"To enable network inspection, add the following pod to your Podfile:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Initialize the plugin in the following way by updating AppDelegate.m:"),(0,o.mdx)(u.default,{defaultValue:"objc",values:[{label:"ObjC",value:"objc"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(p.default,{value:"objc",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\n[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];\n"))),(0,o.mdx)(p.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nclient?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))\n")))),(0,o.mdx)("h2",{id:"protobuf--retrofit-setup"},"Protobuf + Retrofit Setup"),(0,o.mdx)("h3",{id:"gradle-dependencies"},"Gradle Dependencies"),(0,o.mdx)("p",null,"Ensure that you already have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"p"},"build.gradle")," including the plugin dependency, as shown in the following example:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},'dependencies {\n implementation "com.squareup.retrofit2:retrofit:2.9.0"\n implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"\n\n // update version below to match latest Flipper client app\n debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"\n}\n')),(0,o.mdx)("h3",{id:"sending-retrofit-service"},"Sending Retrofit Service"),(0,o.mdx)("p",null,"If you have a Retrofit service interface ",(0,o.mdx)("inlineCode",{parentName:"p"},"PersonService")," which has Protobuf body or return types then at the time you create your implementation, call the plugin with your ",(0,o.mdx)("inlineCode",{parentName:"p"},"baseURL")," and service class, as follows:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre"},"import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit\n...\nval personService = retrofit.create(PersonService::class.java)\nSendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)\n")))}b.isMDXComponent=!0},85162:(e,t,r)=>{r.r(t),r.d(t,{default:()=>l});var n=r(67294),a=r(86010);const o="tabItem_Ymn6";function l(e){var t=e.children,r=e.hidden,l=e.className;return n.createElement("div",{role:"tabpanel",className:(0,a.default)(o,l),hidden:r},t)}},74866:(e,t,r)=>{r.r(t),r.d(t,{default:()=>x});var n=r(83117),a=r(67294),o=r(86010),l=r(12466),i=r(76775),u=r(91980),p=r(67392),c=r(50012);function d(e){return function(e){var t,r;return null!=(t=null==(r=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:r.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function s(e){var t=e.values,r=e.children;return(0,a.useMemo)((function(){var e=null!=t?t:d(r);return function(e){var t=(0,p.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,r])}function f(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function m(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId,o=(0,i.k6)(),l=function(e){var t=e.queryString,r=void 0!==t&&t,n=e.groupId;if("string"==typeof r)return r;if(!1===r)return null;if(!0===r&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=n?n:null}({queryString:r,groupId:n});return[(0,u._X)(l),(0,a.useCallback)((function(e){if(l){var t=new URLSearchParams(o.location.search);t.set(l,e),o.replace(Object.assign({},o.location,{search:t.toString()}))}}),[l,o])]}function v(e){var t,r,n,o,l=e.defaultValue,i=e.queryString,u=void 0!==i&&i,p=e.groupId,d=s(e),v=(0,a.useState)((function(){return function(e){var t,r=e.defaultValue,n=e.tabValues;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(r){if(!f({value:r,tabValues:n}))throw new Error('Docusaurus error: The has a defaultValue "'+r+'" but none of its children has the corresponding value. Available values are: '+n.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return r}var a=null!=(t=n.find((function(e){return e.default})))?t:n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:l,tabValues:d})})),b=v[0],g=v[1],h=m({queryString:u,groupId:p}),y=h[0],w=h[1],k=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:p}.groupId),r=(0,c.Nk)(t),n=r[0],o=r[1],[n,(0,a.useCallback)((function(e){t&&o.set(e)}),[t,o])]),x=k[0],N=k[1],O=function(){var e=null!=y?y:x;return f({value:e,tabValues:d})?e:null}();return(0,a.useLayoutEffect)((function(){O&&g(O)}),[O]),{selectedValue:b,selectValue:(0,a.useCallback)((function(e){if(!f({value:e,tabValues:d}))throw new Error("Can't select invalid tab value="+e);g(e),w(e),N(e)}),[w,N,d]),tabValues:d}}var b=r(72389);const g="tabList__CuJ",h="tabItem_LNqP";function y(e){var t=e.className,r=e.block,i=e.selectedValue,u=e.selectValue,p=e.tabValues,c=[],d=(0,l.o5)().blockElementScrollPositionUntilNextRender,s=function(e){var t=e.currentTarget,r=c.indexOf(t),n=p[r].value;n!==i&&(d(t),u(n))},f=function(e){var t,r=null;switch(e.key){case"Enter":s(e);break;case"ArrowRight":var n,a=c.indexOf(e.currentTarget)+1;r=null!=(n=c[a])?n:c[0];break;case"ArrowLeft":var o,l=c.indexOf(e.currentTarget)-1;r=null!=(o=c[l])?o:c[c.length-1]}null==(t=r)||t.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.default)("tabs",{"tabs--block":r},t)},p.map((function(e){var t=e.value,r=e.label,l=e.attributes;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:function(e){return c.push(e)},onKeyDown:f,onClick:s},l,{className:(0,o.default)("tabs__item",h,null==l?void 0:l.className,{"tabs__item--active":i===t})}),null!=r?r:t)})))}function w(e){var t=e.lazy,r=e.children,n=e.selectedValue,o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){var l=o.find((function(e){return e.props.value===n}));return l?(0,a.cloneElement)(l,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map((function(e,t){return(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n})})))}function k(e){var t=v(e);return a.createElement("div",{className:(0,o.default)("tabs-container",g)},a.createElement(y,(0,n.Z)({},e,t)),a.createElement(w,(0,n.Z)({},e,t)))}function x(e){var t=(0,b.default)();return a.createElement(k,(0,n.Z)({key:String(t)},e))}}}]); \ No newline at end of file diff --git a/assets/js/61ce1e27.8ca31dc7.js b/assets/js/61ce1e27.d16b23c2.js similarity index 98% rename from assets/js/61ce1e27.8ca31dc7.js rename to assets/js/61ce1e27.d16b23c2.js index ddcceb7fca2..438d2195039 100644 --- a/assets/js/61ce1e27.8ca31dc7.js +++ b/assets/js/61ce1e27.d16b23c2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1699,428],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>c,MDXProvider:()=>d,mdx:()=>y,useMDXComponents:()=>u,withMDXComponents:()=>s});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(){return o=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var c=r.createContext({}),s=function(e){return function(n){var t=u(n.components);return r.createElement(e,o({},n,{components:t}))}},u=function(e){var n=r.useContext(c),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=u(e.components);return r.createElement(c.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,i=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),s=u(t),d=a,f=s["".concat(i,".").concat(d)]||s[d]||m[d]||o;return t?r.createElement(f,l(l({ref:n},c),{},{components:t})):r.createElement(f,l({ref:n},c))}));function y(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{contentTitle:()=>s,default:()=>f,frontMatter:()=>c,metadata:()=>u,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(44996),l=t(39960),p=["components"],c={},s=void 0,u={type:"mdx",permalink:"/docs/plugins/leak-canary/setup",source:"@site/src/embedded-pages/docs/plugins/leak-canary/setup.mdx",description:"To setup the LeakCanary plugin, take the following steps:",frontMatter:{}},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,p);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To setup the ",(0,o.mdx)(l.default,{to:(0,i.default)("/docs/features/plugins/leak-canary"),mdxType:"Link"},"LeakCanary plugin"),", take the following steps:"),(0,o.mdx)("ol",null,(0,o.mdx)("li",{parentName:"ol"},"Ensure that you have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"li"},"build.gradle")," including the plugin dependency, such as is shown in the following snippet:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.259.0'\n debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'\n}\n")),(0,o.mdx)("ol",{start:2},(0,o.mdx)("li",{parentName:"ol"},"Update your the ",(0,o.mdx)("inlineCode",{parentName:"li"},"onCreate")," method in you ",(0,o.mdx)("inlineCode",{parentName:"li"},"Application")," to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kt"},"import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener\nimport com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin\n\n...\n\n override fun onCreate() {\n super.onCreate()\n\n /*\n set the flipper listener in leak canary config\n */\n LeakCanary.config = LeakCanary.config.run {\n copy(eventListeners = eventListeners + FlipperLeakEventListener())\n }\n\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n /*\n add leak canary plugin to flipper\n */\n client.addPlugin(LeakCanary2FlipperPlugin())\n client.start()\n }\n }\n")))}f.isMDXComponent=!0},48936:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>c,default:()=>f,frontMatter:()=>p,metadata:()=>s,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(46028),l=["components"],p={id:"leak-canary",title:"LeakCanary Plugin Setup",sidebar_label:"LeakCanary",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx"},c=void 0,s={unversionedId:"setup/plugins/leak-canary",id:"setup/plugins/leak-canary",title:"LeakCanary Plugin Setup",description:"",source:"@site/../docs/setup/plugins/leak-canary.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/leak-canary",permalink:"/docs/setup/plugins/leak-canary",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"leak-canary",title:"LeakCanary Plugin Setup",sidebar_label:"LeakCanary",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx"},sidebar:"main",previous:{title:"Layout",permalink:"/docs/setup/plugins/inspector"},next:{title:"Navigation",permalink:"/docs/setup/plugins/navigation"}},u={},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,l);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)(i.default,{mdxType:"Article"}))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1699,428],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>c,MDXProvider:()=>d,mdx:()=>y,useMDXComponents:()=>u,withMDXComponents:()=>s});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(){return o=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var c=r.createContext({}),s=function(e){return function(n){var t=u(n.components);return r.createElement(e,o({},n,{components:t}))}},u=function(e){var n=r.useContext(c),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},d=function(e){var n=u(e.components);return r.createElement(c.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,i=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),s=u(t),d=a,f=s["".concat(i,".").concat(d)]||s[d]||m[d]||o;return t?r.createElement(f,l(l({ref:n},c),{},{components:t})):r.createElement(f,l({ref:n},c))}));function y(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var c=2;c{t.r(n),t.d(n,{contentTitle:()=>s,default:()=>f,frontMatter:()=>c,metadata:()=>u,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(44996),l=t(39960),p=["components"],c={},s=void 0,u={type:"mdx",permalink:"/docs/plugins/leak-canary/setup",source:"@site/src/embedded-pages/docs/plugins/leak-canary/setup.mdx",description:"To setup the LeakCanary plugin, take the following steps:",frontMatter:{}},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,p);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To setup the ",(0,o.mdx)(l.default,{to:(0,i.default)("/docs/features/plugins/leak-canary"),mdxType:"Link"},"LeakCanary plugin"),", take the following steps:"),(0,o.mdx)("ol",null,(0,o.mdx)("li",{parentName:"ol"},"Ensure that you have an explicit dependency in your application's ",(0,o.mdx)("inlineCode",{parentName:"li"},"build.gradle")," including the plugin dependency, such as is shown in the following snippet:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.260.0'\n debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'\n}\n")),(0,o.mdx)("ol",{start:2},(0,o.mdx)("li",{parentName:"ol"},"Update your the ",(0,o.mdx)("inlineCode",{parentName:"li"},"onCreate")," method in you ",(0,o.mdx)("inlineCode",{parentName:"li"},"Application")," to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:")),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-kt"},"import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener\nimport com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin\n\n...\n\n override fun onCreate() {\n super.onCreate()\n\n /*\n set the flipper listener in leak canary config\n */\n LeakCanary.config = LeakCanary.config.run {\n copy(eventListeners = eventListeners + FlipperLeakEventListener())\n }\n\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n /*\n add leak canary plugin to flipper\n */\n client.addPlugin(LeakCanary2FlipperPlugin())\n client.start()\n }\n }\n")))}f.isMDXComponent=!0},48936:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>c,default:()=>f,frontMatter:()=>p,metadata:()=>s,toc:()=>d});var r=t(83117),a=t(80102),o=(t(67294),t(3905)),i=t(46028),l=["components"],p={id:"leak-canary",title:"LeakCanary Plugin Setup",sidebar_label:"LeakCanary",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx"},c=void 0,s={unversionedId:"setup/plugins/leak-canary",id:"setup/plugins/leak-canary",title:"LeakCanary Plugin Setup",description:"",source:"@site/../docs/setup/plugins/leak-canary.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/leak-canary",permalink:"/docs/setup/plugins/leak-canary",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"leak-canary",title:"LeakCanary Plugin Setup",sidebar_label:"LeakCanary",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/leak_canary/docs/setup.mdx"},sidebar:"main",previous:{title:"Layout",permalink:"/docs/setup/plugins/inspector"},next:{title:"Navigation",permalink:"/docs/setup/plugins/navigation"}},u={},d=[],m={toc:d};function f(e){var n=e.components,t=(0,a.Z)(e,l);return(0,o.mdx)("wrapper",(0,r.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,o.mdx)(i.default,{mdxType:"Article"}))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a37a44be.7206509d.js b/assets/js/a37a44be.5fe16789.js similarity index 99% rename from assets/js/a37a44be.7206509d.js rename to assets/js/a37a44be.5fe16789.js index 0f5f8aca714..1c4f975d004 100644 --- a/assets/js/a37a44be.7206509d.js +++ b/assets/js/a37a44be.5fe16789.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6190,6033],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>u,MDXProvider:()=>d,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>s});var r=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){return o=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=r.createContext({}),s=function(e){return function(t){var n=c(t.components);return r.createElement(e,o({},t,{components:n}))}},c=function(e){var t=r.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return r.createElement(u.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,a=e.parentName,u=p(e,["components","mdxType","originalType","parentName"]),s=c(n),d=i,f=s["".concat(a,".").concat(d)]||s[d]||m[d]||o;return n?r.createElement(f,l(l({ref:t},u),{},{components:n})):r.createElement(f,l({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,a=new Array(o);a[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:i,a[1]=l;for(var u=2;u{n.r(t),n.d(t,{contentTitle:()=>d,default:()=>g,frontMatter:()=>c,metadata:()=>m,toc:()=>f});var r=n(83117),i=n(80102),o=(n(67294),n(3905)),a=n(44996),l=n(39960),p=n(74866),u=n(85162),s=["components"],c={},d=void 0,m={type:"mdx",permalink:"/docs/plugins/inspector/setup",source:"@site/src/embedded-pages/docs/plugins/inspector/setup.mdx",description:"To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},f=[{value:"Android",id:"android",level:2},{value:"Standard Android view only",id:"standard-android-view-only",level:3},{value:"With Litho Support",id:"with-litho-support",level:3},{value:"Blocking fullscreen views (Android only)",id:"blocking-fullscreen-views-android-only",level:3},{value:"Blocking empty view groups (Android only)",id:"blocking-empty-view-groups-android-only",level:3},{value:"iOS",id:"ios",level:2},{value:"Standard UIView Only",id:"standard-uiview-only",level:3},{value:"With ComponentKit Support",id:"with-componentkit-support",level:3}],h={toc:f};function g(e){var t=e.components,n=(0,i.Z)(e,s);return(0,o.mdx)("wrapper",(0,r.Z)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To use the ",(0,o.mdx)(l.default,{to:(0,a.default)("/docs/features/plugins/inspector"),mdxType:"Link"},"Layout Inspector plugin"),", you need to add the plugin to your Flipper client instance."),(0,o.mdx)("h2",{id:"android"},"Android"),(0,o.mdx)("h3",{id:"standard-android-view-only"},"Standard Android view only"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,o.mdx)("h3",{id:"with-litho-support"},"With Litho Support"),(0,o.mdx)("p",null,"Litho support is provided via an optional plugin."),(0,o.mdx)("p",null,"You also need to compile in the ",(0,o.mdx)("inlineCode",{parentName:"p"},"litho-annotations")," package, as Flipper reflects on them at runtime. So ensure to not just include them as ",(0,o.mdx)("inlineCode",{parentName:"p"},"compileOnly")," in your gradle configuration:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.259.0'\n debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'\n // ...\n}\n")),(0,o.mdx)("p",null,"If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies."),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.litho.config.ComponentsConfiguration;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\nimport com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;\n\n// Instead of hard-coding this setting, it's a good practice to tie\n// this to a BuildConfig flag, that you only enable for debug builds\n// of your application.\nComponentsConfiguration.isDebugModeEnabled = true;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n// This adds Litho capabilities to the layout inspector.\nLithoFlipperDescriptors.add(descriptorMapping);\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,o.mdx)("h3",{id:"blocking-fullscreen-views-android-only"},"Blocking fullscreen views (Android only)"),(0,o.mdx)("p",null,"There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary."),(0,o.mdx)("p",null,"Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"view.setTag(R.id.flipper_skip_view_traversal, true);\n")),(0,o.mdx)("h3",{id:"blocking-empty-view-groups-android-only"},"Blocking empty view groups (Android only)"),(0,o.mdx)("p",null,"If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment."),(0,o.mdx)("p",null,"Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker."),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);\n")),(0,o.mdx)("h2",{id:"ios"},"iOS"),(0,o.mdx)("h3",{id:"standard-uiview-only"},"Standard UIView Only"),(0,o.mdx)("p",null,"To debug layout using Flipper, add the following pod:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Once you have added the pod, initialise the plugin and add it to the ",(0,o.mdx)("inlineCode",{parentName:"p"},"FlipperClient")," as follows."),(0,o.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\nSKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];\n"))),(0,o.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))),(0,o.mdx)("h3",{id:"with-componentkit-support"},"With ComponentKit Support"),(0,o.mdx)("p",null,"If you want to enable ",(0,o.mdx)("a",{parentName:"p",href:"https://github.com/facebook/componentkit"},"ComponentKit support")," in the Layout Inspector, you need to add ",(0,o.mdx)("inlineCode",{parentName:"p"},"FlipperKit/FlipperKitLayoutComponentKitSupport")," to your Podfile:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below."),(0,o.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n#import \n\nSKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];\n[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application\n withDescriptorMapper: layoutDescriptorMapper]];\n"))),(0,o.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nFlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))))}g.isMDXComponent=!0},85162:(e,t,n)=>{n.r(t),n.d(t,{default:()=>a});var r=n(67294),i=n(86010);const o="tabItem_Ymn6";function a(e){var t=e.children,n=e.hidden,a=e.className;return r.createElement("div",{role:"tabpanel",className:(0,i.default)(o,a),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>k});var r=n(83117),i=n(67294),o=n(86010),a=n(12466),l=n(76775),p=n(91980),u=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=i.Children.map(e,(function(e){if(!e||(0,i.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,i.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,u.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function m(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function f(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId,o=(0,l.k6)(),a=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:r});return[(0,p._X)(a),(0,i.useCallback)((function(e){if(a){var t=new URLSearchParams(o.location.search);t.set(a,e),o.replace(Object.assign({},o.location,{search:t.toString()}))}}),[a,o])]}function h(e){var t,n,r,o,a=e.defaultValue,l=e.queryString,p=void 0!==l&&l,u=e.groupId,c=d(e),h=(0,i.useState)((function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var i=null!=(t=r.find((function(e){return e.default})))?t:r[0];if(!i)throw new Error("Unexpected error: 0 tabValues");return i.value}({defaultValue:a,tabValues:c})})),g=h[0],v=h[1],y=f({queryString:p,groupId:u}),b=y[0],w=y[1],x=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:u}.groupId),n=(0,s.Nk)(t),r=n[0],o=n[1],[r,(0,i.useCallback)((function(e){t&&o.set(e)}),[t,o])]),k=x[0],D=x[1],T=function(){var e=null!=b?b:k;return m({value:e,tabValues:c})?e:null}();return(0,i.useLayoutEffect)((function(){T&&v(T)}),[T]),{selectedValue:g,selectValue:(0,i.useCallback)((function(e){if(!m({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);v(e),w(e),D(e)}),[w,D,c]),tabValues:c}}var g=n(72389);const v="tabList__CuJ",y="tabItem_LNqP";function b(e){var t=e.className,n=e.block,l=e.selectedValue,p=e.selectValue,u=e.tabValues,s=[],c=(0,a.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),r=u[n].value;r!==l&&(c(t),p(r))},m=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var r,i=s.indexOf(e.currentTarget)+1;n=null!=(r=s[i])?r:s[0];break;case"ArrowLeft":var o,a=s.indexOf(e.currentTarget)-1;n=null!=(o=s[a])?o:s[s.length-1]}null==(t=n)||t.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.default)("tabs",{"tabs--block":n},t)},u.map((function(e){var t=e.value,n=e.label,a=e.attributes;return i.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:function(e){return s.push(e)},onKeyDown:m,onClick:d},a,{className:(0,o.default)("tabs__item",y,null==a?void 0:a.className,{"tabs__item--active":l===t})}),null!=n?n:t)})))}function w(e){var t=e.lazy,n=e.children,r=e.selectedValue,o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var a=o.find((function(e){return e.props.value===r}));return a?(0,i.cloneElement)(a,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map((function(e,t){return(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==r})})))}function x(e){var t=h(e);return i.createElement("div",{className:(0,o.default)("tabs-container",v)},i.createElement(b,(0,r.Z)({},e,t)),i.createElement(w,(0,r.Z)({},e,t)))}function k(e){var t=(0,g.default)();return i.createElement(x,(0,r.Z)({key:String(t)},e))}},79207:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>u,default:()=>f,frontMatter:()=>p,metadata:()=>s,toc:()=>d});var r=n(83117),i=n(80102),o=(n(67294),n(3905)),a=n(31879),l=["components"],p={id:"inspector",title:"Layout Plugin Setup",sidebar_label:"Layout",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx"},u=void 0,s={unversionedId:"setup/plugins/inspector",id:"setup/plugins/inspector",title:"Layout Plugin Setup",description:"",source:"@site/../docs/setup/plugins/inspector.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/inspector",permalink:"/docs/setup/plugins/inspector",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"inspector",title:"Layout Plugin Setup",sidebar_label:"Layout",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx"},sidebar:"main",previous:{title:"Images",permalink:"/docs/setup/plugins/fresco"},next:{title:"LeakCanary",permalink:"/docs/setup/plugins/leak-canary"}},c={},d=[],m={toc:d};function f(e){var t=e.components,n=(0,i.Z)(e,l);return(0,o.mdx)("wrapper",(0,r.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)(a.default,{mdxType:"Article"}))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6190,6033],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>u,MDXProvider:()=>d,mdx:()=>h,useMDXComponents:()=>c,withMDXComponents:()=>s});var r=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(){return o=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=r.createContext({}),s=function(e){return function(t){var n=c(t.components);return r.createElement(e,o({},t,{components:n}))}},c=function(e){var t=r.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},d=function(e){var t=c(e.components);return r.createElement(u.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,a=e.parentName,u=p(e,["components","mdxType","originalType","parentName"]),s=c(n),d=i,f=s["".concat(a,".").concat(d)]||s[d]||m[d]||o;return n?r.createElement(f,l(l({ref:t},u),{},{components:n})):r.createElement(f,l({ref:t},u))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,a=new Array(o);a[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:i,a[1]=l;for(var u=2;u{n.r(t),n.d(t,{contentTitle:()=>d,default:()=>g,frontMatter:()=>c,metadata:()=>m,toc:()=>f});var r=n(83117),i=n(80102),o=(n(67294),n(3905)),a=n(44996),l=n(39960),p=n(74866),u=n(85162),s=["components"],c={},d=void 0,m={type:"mdx",permalink:"/docs/plugins/inspector/setup",source:"@site/src/embedded-pages/docs/plugins/inspector/setup.mdx",description:"To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.",frontMatter:{}},f=[{value:"Android",id:"android",level:2},{value:"Standard Android view only",id:"standard-android-view-only",level:3},{value:"With Litho Support",id:"with-litho-support",level:3},{value:"Blocking fullscreen views (Android only)",id:"blocking-fullscreen-views-android-only",level:3},{value:"Blocking empty view groups (Android only)",id:"blocking-empty-view-groups-android-only",level:3},{value:"iOS",id:"ios",level:2},{value:"Standard UIView Only",id:"standard-uiview-only",level:3},{value:"With ComponentKit Support",id:"with-componentkit-support",level:3}],h={toc:f};function g(e){var t=e.components,n=(0,i.Z)(e,s);return(0,o.mdx)("wrapper",(0,r.Z)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)("p",null,"To use the ",(0,o.mdx)(l.default,{to:(0,a.default)("/docs/features/plugins/inspector"),mdxType:"Link"},"Layout Inspector plugin"),", you need to add the plugin to your Flipper client instance."),(0,o.mdx)("h2",{id:"android"},"Android"),(0,o.mdx)("h3",{id:"standard-android-view-only"},"Standard Android view only"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,o.mdx)("h3",{id:"with-litho-support"},"With Litho Support"),(0,o.mdx)("p",null,"Litho support is provided via an optional plugin."),(0,o.mdx)("p",null,"You also need to compile in the ",(0,o.mdx)("inlineCode",{parentName:"p"},"litho-annotations")," package, as Flipper reflects on them at runtime. So ensure to not just include them as ",(0,o.mdx)("inlineCode",{parentName:"p"},"compileOnly")," in your gradle configuration:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.260.0'\n debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'\n // ...\n}\n")),(0,o.mdx)("p",null,"If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies."),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.litho.config.ComponentsConfiguration;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\nimport com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;\n\n// Instead of hard-coding this setting, it's a good practice to tie\n// this to a BuildConfig flag, that you only enable for debug builds\n// of your application.\nComponentsConfiguration.isDebugModeEnabled = true;\n\nfinal DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();\n// This adds Litho capabilities to the layout inspector.\nLithoFlipperDescriptors.add(descriptorMapping);\n\nclient.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));\n")),(0,o.mdx)("h3",{id:"blocking-fullscreen-views-android-only"},"Blocking fullscreen views (Android only)"),(0,o.mdx)("p",null,"There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary."),(0,o.mdx)("p",null,"Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"view.setTag(R.id.flipper_skip_view_traversal, true);\n")),(0,o.mdx)("h3",{id:"blocking-empty-view-groups-android-only"},"Blocking empty view groups (Android only)"),(0,o.mdx)("p",null,"If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment."),(0,o.mdx)("p",null,"Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker."),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-java"},"viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);\n")),(0,o.mdx)("h2",{id:"ios"},"iOS"),(0,o.mdx)("h3",{id:"standard-uiview-only"},"Standard UIView Only"),(0,o.mdx)("p",null,"To debug layout using Flipper, add the following pod:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Once you have added the pod, initialise the plugin and add it to the ",(0,o.mdx)("inlineCode",{parentName:"p"},"FlipperClient")," as follows."),(0,o.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n\nSKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];\n"))),(0,o.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))),(0,o.mdx)("h3",{id:"with-componentkit-support"},"With ComponentKit Support"),(0,o.mdx)("p",null,"If you want to enable ",(0,o.mdx)("a",{parentName:"p",href:"https://github.com/facebook/componentkit"},"ComponentKit support")," in the Layout Inspector, you need to add ",(0,o.mdx)("inlineCode",{parentName:"p"},"FlipperKit/FlipperKitLayoutComponentKitSupport")," to your Podfile:"),(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-ruby"},"pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version\n")),(0,o.mdx)("p",null,"Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below."),(0,o.mdx)(p.default,{defaultValue:"ios",values:[{label:"iOS",value:"ios"},{label:"Swift",value:"swift"}],mdxType:"Tabs"},(0,o.mdx)(u.default,{value:"ios",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-objc"},"#import \n#import \n\nSKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];\n[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];\n[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application\n withDescriptorMapper: layoutDescriptorMapper]];\n"))),(0,o.mdx)(u.default,{value:"swift",mdxType:"TabItem"},(0,o.mdx)("pre",null,(0,o.mdx)("code",{parentName:"pre",className:"language-swift"},"import FlipperKit\n\nlet layoutDescriptorMapper = SKDescriptorMapper(defaults: ())\nFlipperKitLayoutComponentKitSupport.setUpWith(layoutDescriptorMapper)\n\nclient?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))\n")))))}g.isMDXComponent=!0},85162:(e,t,n)=>{n.r(t),n.d(t,{default:()=>a});var r=n(67294),i=n(86010);const o="tabItem_Ymn6";function a(e){var t=e.children,n=e.hidden,a=e.className;return r.createElement("div",{role:"tabpanel",className:(0,i.default)(o,a),hidden:n},t)}},74866:(e,t,n)=>{n.r(t),n.d(t,{default:()=>k});var r=n(83117),i=n(67294),o=n(86010),a=n(12466),l=n(76775),p=n(91980),u=n(67392),s=n(50012);function c(e){return function(e){var t,n;return null!=(t=null==(n=i.Children.map(e,(function(e){if(!e||(0,i.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return e;var t;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:n.filter(Boolean))?t:[]}(e).map((function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}}))}function d(e){var t=e.values,n=e.children;return(0,i.useMemo)((function(){var e=null!=t?t:c(n);return function(e){var t=(0,u.l)(e,(function(e,t){return e.value===t.value}));if(t.length>0)throw new Error('Docusaurus error: Duplicate values "'+t.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[t,n])}function m(e){var t=e.value;return e.tabValues.some((function(e){return e.value===t}))}function f(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId,o=(0,l.k6)(),a=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:r});return[(0,p._X)(a),(0,i.useCallback)((function(e){if(a){var t=new URLSearchParams(o.location.search);t.set(a,e),o.replace(Object.assign({},o.location,{search:t.toString()}))}}),[a,o])]}function h(e){var t,n,r,o,a=e.defaultValue,l=e.queryString,p=void 0!==l&&l,u=e.groupId,c=d(e),h=(0,i.useState)((function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var i=null!=(t=r.find((function(e){return e.default})))?t:r[0];if(!i)throw new Error("Unexpected error: 0 tabValues");return i.value}({defaultValue:a,tabValues:c})})),g=h[0],v=h[1],y=f({queryString:p,groupId:u}),b=y[0],w=y[1],x=(t=function(e){return e?"docusaurus.tab."+e:null}({groupId:u}.groupId),n=(0,s.Nk)(t),r=n[0],o=n[1],[r,(0,i.useCallback)((function(e){t&&o.set(e)}),[t,o])]),k=x[0],D=x[1],T=function(){var e=null!=b?b:k;return m({value:e,tabValues:c})?e:null}();return(0,i.useLayoutEffect)((function(){T&&v(T)}),[T]),{selectedValue:g,selectValue:(0,i.useCallback)((function(e){if(!m({value:e,tabValues:c}))throw new Error("Can't select invalid tab value="+e);v(e),w(e),D(e)}),[w,D,c]),tabValues:c}}var g=n(72389);const v="tabList__CuJ",y="tabItem_LNqP";function b(e){var t=e.className,n=e.block,l=e.selectedValue,p=e.selectValue,u=e.tabValues,s=[],c=(0,a.o5)().blockElementScrollPositionUntilNextRender,d=function(e){var t=e.currentTarget,n=s.indexOf(t),r=u[n].value;r!==l&&(c(t),p(r))},m=function(e){var t,n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":var r,i=s.indexOf(e.currentTarget)+1;n=null!=(r=s[i])?r:s[0];break;case"ArrowLeft":var o,a=s.indexOf(e.currentTarget)-1;n=null!=(o=s[a])?o:s[s.length-1]}null==(t=n)||t.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.default)("tabs",{"tabs--block":n},t)},u.map((function(e){var t=e.value,n=e.label,a=e.attributes;return i.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:function(e){return s.push(e)},onKeyDown:m,onClick:d},a,{className:(0,o.default)("tabs__item",y,null==a?void 0:a.className,{"tabs__item--active":l===t})}),null!=n?n:t)})))}function w(e){var t=e.lazy,n=e.children,r=e.selectedValue,o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){var a=o.find((function(e){return e.props.value===r}));return a?(0,i.cloneElement)(a,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map((function(e,t){return(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==r})})))}function x(e){var t=h(e);return i.createElement("div",{className:(0,o.default)("tabs-container",v)},i.createElement(b,(0,r.Z)({},e,t)),i.createElement(w,(0,r.Z)({},e,t)))}function k(e){var t=(0,g.default)();return i.createElement(x,(0,r.Z)({key:String(t)},e))}},79207:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>u,default:()=>f,frontMatter:()=>p,metadata:()=>s,toc:()=>d});var r=n(83117),i=n(80102),o=(n(67294),n(3905)),a=n(31879),l=["components"],p={id:"inspector",title:"Layout Plugin Setup",sidebar_label:"Layout",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx"},u=void 0,s={unversionedId:"setup/plugins/inspector",id:"setup/plugins/inspector",title:"Layout Plugin Setup",description:"",source:"@site/../docs/setup/plugins/inspector.mdx",sourceDirName:"setup/plugins",slug:"/setup/plugins/inspector",permalink:"/docs/setup/plugins/inspector",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx",tags:[],version:"current",frontMatter:{id:"inspector",title:"Layout Plugin Setup",sidebar_label:"Layout",custom_edit_url:"https://github.com/facebook/flipper/blob/main/desktop/plugins/public/layout/docs/setup.mdx"},sidebar:"main",previous:{title:"Images",permalink:"/docs/setup/plugins/fresco"},next:{title:"LeakCanary",permalink:"/docs/setup/plugins/leak-canary"}},c={},d=[],m={toc:d};function f(e){var t=e.components,n=(0,i.Z)(e,l);return(0,o.mdx)("wrapper",(0,r.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,o.mdx)(a.default,{mdxType:"Article"}))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/dc9e5b6f.6d1923d4.js b/assets/js/dc9e5b6f.86af65ec.js similarity index 98% rename from assets/js/dc9e5b6f.6d1923d4.js rename to assets/js/dc9e5b6f.86af65ec.js index e32bfff167c..4f5ea31d80b 100644 --- a/assets/js/dc9e5b6f.6d1923d4.js +++ b/assets/js/dc9e5b6f.86af65ec.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2165],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>s,MDXProvider:()=>c,mdx:()=>h,useMDXComponents:()=>d,withMDXComponents:()=>u});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(){return i=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),u=function(e){return function(n){var t=d(n.components);return r.createElement(e,i({},n,{components:t}))}},d=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},c=function(e){var n=d(e.components);return r.createElement(s.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,s=p(e,["components","mdxType","originalType","parentName"]),u=d(t),c=a,f=u["".concat(o,".").concat(c)]||u[c]||m[c]||i;return t?r.createElement(f,l(l({ref:n},s),{},{components:t})):r.createElement(f,l({ref:n},s))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,o=new Array(i);o[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,o[1]=l;for(var s=2;s{t.r(n),t.d(n,{default:()=>o});var r=t(67294),a=t(86010);const i="tabItem_Ymn6";function o(e){var n=e.children,t=e.hidden,o=e.className;return r.createElement("div",{role:"tabpanel",className:(0,a.default)(i,o),hidden:t},n)}},74866:(e,n,t)=>{t.r(n),t.d(n,{default:()=>k});var r=t(83117),a=t(67294),i=t(86010),o=t(12466),l=t(76775),p=t(91980),s=t(67392),u=t(50012);function d(e){return function(e){var n,t;return null!=(n=null==(t=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(n=e.props)&&"object"==typeof n&&"value"in n)return e;var n;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:t.filter(Boolean))?n:[]}(e).map((function(e){var n=e.props;return{value:n.value,label:n.label,attributes:n.attributes,default:n.default}}))}function c(e){var n=e.values,t=e.children;return(0,a.useMemo)((function(){var e=null!=n?n:d(t);return function(e){var n=(0,s.l)(e,(function(e,n){return e.value===n.value}));if(n.length>0)throw new Error('Docusaurus error: Duplicate values "'+n.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[n,t])}function m(e){var n=e.value;return e.tabValues.some((function(e){return e.value===n}))}function f(e){var n=e.queryString,t=void 0!==n&&n,r=e.groupId,i=(0,l.k6)(),o=function(e){var n=e.queryString,t=void 0!==n&&n,r=e.groupId;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:t,groupId:r});return[(0,p._X)(o),(0,a.useCallback)((function(e){if(o){var n=new URLSearchParams(i.location.search);n.set(o,e),i.replace(Object.assign({},i.location,{search:n.toString()}))}}),[o,i])]}function h(e){var n,t,r,i,o=e.defaultValue,l=e.queryString,p=void 0!==l&&l,s=e.groupId,d=c(e),h=(0,a.useState)((function(){return function(e){var n,t=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+t+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return t}var a=null!=(n=r.find((function(e){return e.default})))?n:r[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:o,tabValues:d})})),g=h[0],v=h[1],b=f({queryString:p,groupId:s}),y=b[0],x=b[1],w=(n=function(e){return e?"docusaurus.tab."+e:null}({groupId:s}.groupId),t=(0,u.Nk)(n),r=t[0],i=t[1],[r,(0,a.useCallback)((function(e){n&&i.set(e)}),[n,i])]),k=w[0],N=w[1],C=function(){var e=null!=y?y:k;return m({value:e,tabValues:d})?e:null}();return(0,a.useLayoutEffect)((function(){C&&v(C)}),[C]),{selectedValue:g,selectValue:(0,a.useCallback)((function(e){if(!m({value:e,tabValues:d}))throw new Error("Can't select invalid tab value="+e);v(e),x(e),N(e)}),[x,N,d]),tabValues:d}}var g=t(72389);const v="tabList__CuJ",b="tabItem_LNqP";function y(e){var n=e.className,t=e.block,l=e.selectedValue,p=e.selectValue,s=e.tabValues,u=[],d=(0,o.o5)().blockElementScrollPositionUntilNextRender,c=function(e){var n=e.currentTarget,t=u.indexOf(n),r=s[t].value;r!==l&&(d(n),p(r))},m=function(e){var n,t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":var r,a=u.indexOf(e.currentTarget)+1;t=null!=(r=u[a])?r:u[0];break;case"ArrowLeft":var i,o=u.indexOf(e.currentTarget)-1;t=null!=(i=u[o])?i:u[u.length-1]}null==(n=t)||n.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.default)("tabs",{"tabs--block":t},n)},s.map((function(e){var n=e.value,t=e.label,o=e.attributes;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:function(e){return u.push(e)},onKeyDown:m,onClick:c},o,{className:(0,i.default)("tabs__item",b,null==o?void 0:o.className,{"tabs__item--active":l===n})}),null!=t?t:n)})))}function x(e){var n=e.lazy,t=e.children,r=e.selectedValue,i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){var o=i.find((function(e){return e.props.value===r}));return o?(0,a.cloneElement)(o,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},i.map((function(e,n){return(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r})})))}function w(e){var n=h(e);return a.createElement("div",{className:(0,i.default)("tabs-container",v)},a.createElement(y,(0,r.Z)({},e,n)),a.createElement(x,(0,r.Z)({},e,n)))}function k(e){var n=(0,g.default)();return a.createElement(w,(0,r.Z)({key:String(n)},e))}},26053:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>f,contentTitle:()=>c,default:()=>v,frontMatter:()=>d,metadata:()=>m,toc:()=>h});var r=t(83117),a=t(80102),i=(t(67294),t(3905)),o=t(44996),l=t(39960),p=t(74866),s=t(85162),u=["components"],d={id:"android-native",title:"Adding Flipper to Android apps with Gradle",sidebar_label:"Android with Gradle"},c=void 0,m={unversionedId:"getting-started/android-native",id:"getting-started/android-native",title:"Adding Flipper to Android apps with Gradle",description:"To set up Flipper for Android, you need to add the necessary dependencies to your app, initialize the Flipper client and enable the plugins you want to use.",source:"@site/../docs/getting-started/android-native.mdx",sourceDirName:"getting-started",slug:"/getting-started/android-native",permalink:"/docs/getting-started/android-native",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/android-native.mdx",tags:[],version:"current",frontMatter:{id:"android-native",title:"Adding Flipper to Android apps with Gradle",sidebar_label:"Android with Gradle"},sidebar:"main",previous:{title:"Desktop App",permalink:"/docs/getting-started/"},next:{title:"Generic iOS Apps",permalink:"/docs/getting-started/ios-native"}},f={},h=[{value:"Dependencies",id:"dependencies",level:2},{value:"Application setup",id:"application-setup",level:2},{value:"Diagnostics",id:"diagnostics",level:2},{value:"Android snapshots",id:"android-snapshots",level:2},{value:"Enabling plugins",id:"enabling-plugins",level:2},{value:"Issues or questions",id:"issues-or-questions",level:2}],g={toc:h};function v(e){var n=e.components,t=(0,a.Z)(e,u);return(0,i.mdx)("wrapper",(0,r.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"To set up Flipper for Android, you need to add the necessary dependencies to your app, initialize the Flipper client and enable the plugins you want to use.\nOptionally, you can hook up the diagnostics Activity to help you troubleshoot connection issues."),(0,i.mdx)("h2",{id:"dependencies"},"Dependencies"),(0,i.mdx)("p",null,"Flipper is distributed via Maven Central: add the dependencies to your ",(0,i.mdx)("inlineCode",{parentName:"p"},"build.gradle")," file."),(0,i.mdx)("p",null,"You should also explicitly depend on ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/soloader"},"SoLoader")," instead of relying on transitive dependency resolution, which is getting deprecated\nwith Gradle 5."),(0,i.mdx)("p",null,"There is a 'no-op' implementation of some oft-used Flipper interfaces, which you can use to make it easier to strip Flipper from your release builds:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-groovy"},"repositories {\n mavenCentral()\n}\n\ndependencies {\n debugImplementation 'com.facebook.flipper:flipper:0.259.0'\n debugImplementation 'com.facebook.soloader:soloader:0.10.5'\n\n releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.0'\n}\n")),(0,i.mdx)("admonition",{type:"warning"},(0,i.mdx)("p",{parentName:"admonition"},"The ",(0,i.mdx)("inlineCode",{parentName:"p"},"flipper-noop")," package provides a limited subset of the APIs provided by the ",(0,i.mdx)("inlineCode",{parentName:"p"},"flipper")," package and does not provide any plugin stubs.\nIt's recommended that you keep all Flipper instantiation code in a separate build variant to ensure it doesn't accidentally make it into your production builds."),(0,i.mdx)("p",{parentName:"admonition"},"To see how to organise your Flipper initialization into debug and release variants, check this ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper/tree/main/android/sample/src"},"sample app"),"."),(0,i.mdx)("p",{parentName:"admonition"},"Alternatively, have a look at the third-party ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/theGlenn/flipper-android-no-op"},"flipper-android-no-op")," repository, which provides empty implementations for several Flipper plugins.")),(0,i.mdx)("h2",{id:"application-setup"},"Application setup"),(0,i.mdx)("p",null,"Now you can initialize Flipper in your Application's ",(0,i.mdx)("inlineCode",{parentName:"p"},"onCreate")," method, which involves initializing SoLoader (for loading the C++ part of Flipper) and starting a ",(0,i.mdx)("inlineCode",{parentName:"p"},"FlipperClient"),"."),(0,i.mdx)(p.default,{mdxType:"Tabs"},(0,i.mdx)(s.default,{value:"kt",label:"Kotlin",mdxType:"TabItem"},(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-kotlin"},"import com.facebook.flipper.android.AndroidFlipperClient\nimport com.facebook.flipper.android.utils.FlipperUtils\nimport com.facebook.flipper.core.FlipperClient\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin\n\nclass MyApplication : Application {\n override fun onCreate() {\n super.onCreate()\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))\n client.start()\n }\n }\n}\n"))),(0,i.mdx)(s.default,{value:"java",label:"Java",mdxType:"TabItem"},(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.android.AndroidFlipperClient;\nimport com.facebook.flipper.android.utils.FlipperUtils;\nimport com.facebook.flipper.core.FlipperClient;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\n\npublic class MyApplication extends Application {\n\n @Override\n public void onCreate() {\n super.onCreate();\n SoLoader.init(this, false);\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n final FlipperClient client = AndroidFlipperClient.getInstance(this);\n client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()));\n client.start();\n }\n }\n}\n")))),(0,i.mdx)("h2",{id:"diagnostics"},"Diagnostics"),(0,i.mdx)("p",null,"It's recommended that you add the following activity to the manifest, which can help diagnose integration issues and other problems:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-xml"},'\n')),(0,i.mdx)("h2",{id:"android-snapshots"},"Android snapshots"),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Android snapshot releases are published directly off ",(0,i.mdx)("inlineCode",{parentName:"p"},"main"),".")),(0,i.mdx)("p",null,"You can get the latest version by adding the Maven Snapshot repository to your sources and pointing to the most recent ",(0,i.mdx)("inlineCode",{parentName:"p"},"-SNAPSHOT")," version."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-groovy"},"repositories {\n maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }\n}\n\ndependencies {\n debugImplementation 'com.facebook.flipper:flipper:0.259.1-SNAPSHOT'\n debugImplementation 'com.facebook.soloader:soloader:0.10.5'\n\n releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.1-SNAPSHOT'\n}\n")),(0,i.mdx)("h2",{id:"enabling-plugins"},"Enabling plugins"),(0,i.mdx)("p",null,"Finally, you need to add plugins to your Flipper client."),(0,i.mdx)("p",null,"Above, the Layout Inspector plugin has been added to get you started. See the ",(0,i.mdx)(l.default,{to:(0,o.default)("/docs/setup/plugins/network"),mdxType:"Link"},"Network Plugin")," and ",(0,i.mdx)("a",{parentName:"p",href:"https://www.internalfb.com/intern/staticdocs/flipper/docs/features/plugins/inspector/"},"Layout Inspector Plugin")," pages for information on how to add them, and also enable Litho or ComponentKit support."),(0,i.mdx)("p",null,"For examples of integrating other plugins, take a look at the sample apps in the ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper"},"GitHub repo"),"."),(0,i.mdx)("h2",{id:"issues-or-questions"},"Issues or questions"),(0,i.mdx)("p",null,"If you encounter any issues or have any questions, refer to the ",(0,i.mdx)("a",{parentName:"p",href:"/docs/getting-started/troubleshooting/"},"Troubleshooting")," section."))}v.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2165],{3905:(e,n,t)=>{t.r(n),t.d(n,{MDXContext:()=>s,MDXProvider:()=>c,mdx:()=>h,useMDXComponents:()=>d,withMDXComponents:()=>u});var r=t(67294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(){return i=Object.assign||function(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),u=function(e){return function(n){var t=d(n.components);return r.createElement(e,i({},n,{components:t}))}},d=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):l(l({},n),e)),t},c=function(e){var n=d(e.components);return r.createElement(s.Provider,{value:n},e.children)},m={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},f=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,i=e.originalType,o=e.parentName,s=p(e,["components","mdxType","originalType","parentName"]),u=d(t),c=a,f=u["".concat(o,".").concat(c)]||u[c]||m[c]||i;return t?r.createElement(f,l(l({ref:n},s),{},{components:t})):r.createElement(f,l({ref:n},s))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var i=t.length,o=new Array(i);o[0]=f;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l.mdxType="string"==typeof e?e:a,o[1]=l;for(var s=2;s{t.r(n),t.d(n,{default:()=>o});var r=t(67294),a=t(86010);const i="tabItem_Ymn6";function o(e){var n=e.children,t=e.hidden,o=e.className;return r.createElement("div",{role:"tabpanel",className:(0,a.default)(i,o),hidden:t},n)}},74866:(e,n,t)=>{t.r(n),t.d(n,{default:()=>k});var r=t(83117),a=t(67294),i=t(86010),o=t(12466),l=t(76775),p=t(91980),s=t(67392),u=t(50012);function d(e){return function(e){var n,t;return null!=(n=null==(t=a.Children.map(e,(function(e){if(!e||(0,a.isValidElement)(e)&&(n=e.props)&&"object"==typeof n&&"value"in n)return e;var n;throw new Error("Docusaurus error: Bad child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the component should be , and every should have a unique "value" prop.')})))?void 0:t.filter(Boolean))?n:[]}(e).map((function(e){var n=e.props;return{value:n.value,label:n.label,attributes:n.attributes,default:n.default}}))}function c(e){var n=e.values,t=e.children;return(0,a.useMemo)((function(){var e=null!=n?n:d(t);return function(e){var n=(0,s.l)(e,(function(e,n){return e.value===n.value}));if(n.length>0)throw new Error('Docusaurus error: Duplicate values "'+n.map((function(e){return e.value})).join(", ")+'" found in . Every value needs to be unique.')}(e),e}),[n,t])}function m(e){var n=e.value;return e.tabValues.some((function(e){return e.value===n}))}function f(e){var n=e.queryString,t=void 0!==n&&n,r=e.groupId,i=(0,l.k6)(),o=function(e){var n=e.queryString,t=void 0!==n&&n,r=e.groupId;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:t,groupId:r});return[(0,p._X)(o),(0,a.useCallback)((function(e){if(o){var n=new URLSearchParams(i.location.search);n.set(o,e),i.replace(Object.assign({},i.location,{search:n.toString()}))}}),[o,i])]}function h(e){var n,t,r,i,o=e.defaultValue,l=e.queryString,p=void 0!==l&&l,s=e.groupId,d=c(e),h=(0,a.useState)((function(){return function(e){var n,t=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:r}))throw new Error('Docusaurus error: The has a defaultValue "'+t+'" but none of its children has the corresponding value. Available values are: '+r.map((function(e){return e.value})).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return t}var a=null!=(n=r.find((function(e){return e.default})))?n:r[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:o,tabValues:d})})),g=h[0],v=h[1],b=f({queryString:p,groupId:s}),y=b[0],x=b[1],w=(n=function(e){return e?"docusaurus.tab."+e:null}({groupId:s}.groupId),t=(0,u.Nk)(n),r=t[0],i=t[1],[r,(0,a.useCallback)((function(e){n&&i.set(e)}),[n,i])]),k=w[0],N=w[1],C=function(){var e=null!=y?y:k;return m({value:e,tabValues:d})?e:null}();return(0,a.useLayoutEffect)((function(){C&&v(C)}),[C]),{selectedValue:g,selectValue:(0,a.useCallback)((function(e){if(!m({value:e,tabValues:d}))throw new Error("Can't select invalid tab value="+e);v(e),x(e),N(e)}),[x,N,d]),tabValues:d}}var g=t(72389);const v="tabList__CuJ",b="tabItem_LNqP";function y(e){var n=e.className,t=e.block,l=e.selectedValue,p=e.selectValue,s=e.tabValues,u=[],d=(0,o.o5)().blockElementScrollPositionUntilNextRender,c=function(e){var n=e.currentTarget,t=u.indexOf(n),r=s[t].value;r!==l&&(d(n),p(r))},m=function(e){var n,t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":var r,a=u.indexOf(e.currentTarget)+1;t=null!=(r=u[a])?r:u[0];break;case"ArrowLeft":var i,o=u.indexOf(e.currentTarget)-1;t=null!=(i=u[o])?i:u[u.length-1]}null==(n=t)||n.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.default)("tabs",{"tabs--block":t},n)},s.map((function(e){var n=e.value,t=e.label,o=e.attributes;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:function(e){return u.push(e)},onKeyDown:m,onClick:c},o,{className:(0,i.default)("tabs__item",b,null==o?void 0:o.className,{"tabs__item--active":l===n})}),null!=t?t:n)})))}function x(e){var n=e.lazy,t=e.children,r=e.selectedValue,i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){var o=i.find((function(e){return e.props.value===r}));return o?(0,a.cloneElement)(o,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},i.map((function(e,n){return(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r})})))}function w(e){var n=h(e);return a.createElement("div",{className:(0,i.default)("tabs-container",v)},a.createElement(y,(0,r.Z)({},e,n)),a.createElement(x,(0,r.Z)({},e,n)))}function k(e){var n=(0,g.default)();return a.createElement(w,(0,r.Z)({key:String(n)},e))}},26053:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>f,contentTitle:()=>c,default:()=>v,frontMatter:()=>d,metadata:()=>m,toc:()=>h});var r=t(83117),a=t(80102),i=(t(67294),t(3905)),o=t(44996),l=t(39960),p=t(74866),s=t(85162),u=["components"],d={id:"android-native",title:"Adding Flipper to Android apps with Gradle",sidebar_label:"Android with Gradle"},c=void 0,m={unversionedId:"getting-started/android-native",id:"getting-started/android-native",title:"Adding Flipper to Android apps with Gradle",description:"To set up Flipper for Android, you need to add the necessary dependencies to your app, initialize the Flipper client and enable the plugins you want to use.",source:"@site/../docs/getting-started/android-native.mdx",sourceDirName:"getting-started",slug:"/getting-started/android-native",permalink:"/docs/getting-started/android-native",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/android-native.mdx",tags:[],version:"current",frontMatter:{id:"android-native",title:"Adding Flipper to Android apps with Gradle",sidebar_label:"Android with Gradle"},sidebar:"main",previous:{title:"Desktop App",permalink:"/docs/getting-started/"},next:{title:"Generic iOS Apps",permalink:"/docs/getting-started/ios-native"}},f={},h=[{value:"Dependencies",id:"dependencies",level:2},{value:"Application setup",id:"application-setup",level:2},{value:"Diagnostics",id:"diagnostics",level:2},{value:"Android snapshots",id:"android-snapshots",level:2},{value:"Enabling plugins",id:"enabling-plugins",level:2},{value:"Issues or questions",id:"issues-or-questions",level:2}],g={toc:h};function v(e){var n=e.components,t=(0,a.Z)(e,u);return(0,i.mdx)("wrapper",(0,r.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.mdx)("p",null,"To set up Flipper for Android, you need to add the necessary dependencies to your app, initialize the Flipper client and enable the plugins you want to use.\nOptionally, you can hook up the diagnostics Activity to help you troubleshoot connection issues."),(0,i.mdx)("h2",{id:"dependencies"},"Dependencies"),(0,i.mdx)("p",null,"Flipper is distributed via Maven Central: add the dependencies to your ",(0,i.mdx)("inlineCode",{parentName:"p"},"build.gradle")," file."),(0,i.mdx)("p",null,"You should also explicitly depend on ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/soloader"},"SoLoader")," instead of relying on transitive dependency resolution, which is getting deprecated\nwith Gradle 5."),(0,i.mdx)("p",null,"There is a 'no-op' implementation of some oft-used Flipper interfaces, which you can use to make it easier to strip Flipper from your release builds:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-groovy"},"repositories {\n mavenCentral()\n}\n\ndependencies {\n debugImplementation 'com.facebook.flipper:flipper:0.260.0'\n debugImplementation 'com.facebook.soloader:soloader:0.10.5'\n\n releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.0'\n}\n")),(0,i.mdx)("admonition",{type:"warning"},(0,i.mdx)("p",{parentName:"admonition"},"The ",(0,i.mdx)("inlineCode",{parentName:"p"},"flipper-noop")," package provides a limited subset of the APIs provided by the ",(0,i.mdx)("inlineCode",{parentName:"p"},"flipper")," package and does not provide any plugin stubs.\nIt's recommended that you keep all Flipper instantiation code in a separate build variant to ensure it doesn't accidentally make it into your production builds."),(0,i.mdx)("p",{parentName:"admonition"},"To see how to organise your Flipper initialization into debug and release variants, check this ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper/tree/main/android/sample/src"},"sample app"),"."),(0,i.mdx)("p",{parentName:"admonition"},"Alternatively, have a look at the third-party ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/theGlenn/flipper-android-no-op"},"flipper-android-no-op")," repository, which provides empty implementations for several Flipper plugins.")),(0,i.mdx)("h2",{id:"application-setup"},"Application setup"),(0,i.mdx)("p",null,"Now you can initialize Flipper in your Application's ",(0,i.mdx)("inlineCode",{parentName:"p"},"onCreate")," method, which involves initializing SoLoader (for loading the C++ part of Flipper) and starting a ",(0,i.mdx)("inlineCode",{parentName:"p"},"FlipperClient"),"."),(0,i.mdx)(p.default,{mdxType:"Tabs"},(0,i.mdx)(s.default,{value:"kt",label:"Kotlin",mdxType:"TabItem"},(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-kotlin"},"import com.facebook.flipper.android.AndroidFlipperClient\nimport com.facebook.flipper.android.utils.FlipperUtils\nimport com.facebook.flipper.core.FlipperClient\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin\n\nclass MyApplication : Application {\n override fun onCreate() {\n super.onCreate()\n SoLoader.init(this, false)\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n val client = AndroidFlipperClient.getInstance(this)\n client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))\n client.start()\n }\n }\n}\n"))),(0,i.mdx)(s.default,{value:"java",label:"Java",mdxType:"TabItem"},(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-java"},"import com.facebook.flipper.android.AndroidFlipperClient;\nimport com.facebook.flipper.android.utils.FlipperUtils;\nimport com.facebook.flipper.core.FlipperClient;\nimport com.facebook.flipper.plugins.inspector.DescriptorMapping;\nimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;\n\n\npublic class MyApplication extends Application {\n\n @Override\n public void onCreate() {\n super.onCreate();\n SoLoader.init(this, false);\n\n if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {\n final FlipperClient client = AndroidFlipperClient.getInstance(this);\n client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()));\n client.start();\n }\n }\n}\n")))),(0,i.mdx)("h2",{id:"diagnostics"},"Diagnostics"),(0,i.mdx)("p",null,"It's recommended that you add the following activity to the manifest, which can help diagnose integration issues and other problems:"),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-xml"},'\n')),(0,i.mdx)("h2",{id:"android-snapshots"},"Android snapshots"),(0,i.mdx)("admonition",{type:"note"},(0,i.mdx)("p",{parentName:"admonition"},"Android snapshot releases are published directly off ",(0,i.mdx)("inlineCode",{parentName:"p"},"main"),".")),(0,i.mdx)("p",null,"You can get the latest version by adding the Maven Snapshot repository to your sources and pointing to the most recent ",(0,i.mdx)("inlineCode",{parentName:"p"},"-SNAPSHOT")," version."),(0,i.mdx)("pre",null,(0,i.mdx)("code",{parentName:"pre",className:"language-groovy"},"repositories {\n maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }\n}\n\ndependencies {\n debugImplementation 'com.facebook.flipper:flipper:0.260.1-SNAPSHOT'\n debugImplementation 'com.facebook.soloader:soloader:0.10.5'\n\n releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.1-SNAPSHOT'\n}\n")),(0,i.mdx)("h2",{id:"enabling-plugins"},"Enabling plugins"),(0,i.mdx)("p",null,"Finally, you need to add plugins to your Flipper client."),(0,i.mdx)("p",null,"Above, the Layout Inspector plugin has been added to get you started. See the ",(0,i.mdx)(l.default,{to:(0,o.default)("/docs/setup/plugins/network"),mdxType:"Link"},"Network Plugin")," and ",(0,i.mdx)("a",{parentName:"p",href:"https://www.internalfb.com/intern/staticdocs/flipper/docs/features/plugins/inspector/"},"Layout Inspector Plugin")," pages for information on how to add them, and also enable Litho or ComponentKit support."),(0,i.mdx)("p",null,"For examples of integrating other plugins, take a look at the sample apps in the ",(0,i.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper"},"GitHub repo"),"."),(0,i.mdx)("h2",{id:"issues-or-questions"},"Issues or questions"),(0,i.mdx)("p",null,"If you encounter any issues or have any questions, refer to the ",(0,i.mdx)("a",{parentName:"p",href:"/docs/getting-started/troubleshooting/"},"Troubleshooting")," section."))}v.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fa2418a3.56863c34.js b/assets/js/fa2418a3.55c7f797.js similarity index 98% rename from assets/js/fa2418a3.56863c34.js rename to assets/js/fa2418a3.55c7f797.js index be268fcc4b3..94d694b165e 100644 --- a/assets/js/fa2418a3.56863c34.js +++ b/assets/js/fa2418a3.55c7f797.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[880],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>d,MDXProvider:()=>s,mdx:()=>g,useMDXComponents:()=>m,withMDXComponents:()=>u});var i=n(67294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=i.createContext({}),u=function(e){return function(t){var n=m(t.components);return i.createElement(e,a({},t,{components:n}))}},m=function(e){var t=i.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},s=function(e){var t=m(e.components);return i.createElement(d.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},f=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=m(n),s=r,f=u["".concat(l,".").concat(s)]||u[s]||c[s]||a;return n?i.createElement(f,o(o({ref:t},d),{},{components:n})):i.createElement(f,o({ref:t},d))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,l=new Array(a);l[0]=f;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>d,metadata:()=>m,toc:()=>c});var i=n(83117),r=n(80102),a=(n(67294),n(3905)),l=n(44996),o=n(39960),p=["components"],d={id:"react-native",title:"React Native App - Automatic Setup",sidebar_label:"Automatic Setup"},u=void 0,m={unversionedId:"getting-started/react-native",id:"getting-started/react-native",title:"React Native App - Automatic Setup",description:"Starting with React Native 0.62, after generating your project with react-native init, the Flipper integration is ready out of the box for debug builds:",source:"@site/../docs/getting-started/react-native.mdx",sourceDirName:"getting-started",slug:"/getting-started/react-native",permalink:"/docs/getting-started/react-native",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/react-native.mdx",tags:[],version:"current",frontMatter:{id:"react-native",title:"React Native App - Automatic Setup",sidebar_label:"Automatic Setup"},sidebar:"main",previous:{title:"Generic iOS Apps",permalink:"/docs/getting-started/ios-native"},next:{title:"Manual Android Setup",permalink:"/docs/getting-started/react-native-android"}},s={},c=[{value:"Using the latest Flipper SDK",id:"using-the-latest-flipper-sdk",level:3},{value:"Manual Setup",id:"manual-setup",level:2}],f={toc:c};function g(e){var t=e.components,n=(0,r.Z)(e,p);return(0,a.mdx)("wrapper",(0,i.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"Starting with React Native 0.62, after generating your project with ",(0,a.mdx)("inlineCode",{parentName:"p"},"react-native init"),", the Flipper integration is ready out of the box for debug builds:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("strong",{parentName:"li"},"Android")," - start the Flipper Desktop application and start your project using ",(0,a.mdx)("inlineCode",{parentName:"li"},"yarn android"),". Your application will appear in Flipper."),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("strong",{parentName:"li"},"iOS")," - run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install")," once in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory of your project. After that, run ",(0,a.mdx)("inlineCode",{parentName:"li"},"yarn ios")," and start Flipper. Your application will show up in Flipper.")),(0,a.mdx)("p",null,"By default, the following plugins will be available:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},"Layout Inspector"),(0,a.mdx)("li",{parentName:"ul"},"Network"),(0,a.mdx)("li",{parentName:"ul"},"Databases"),(0,a.mdx)("li",{parentName:"ul"},"Images"),(0,a.mdx)("li",{parentName:"ul"},"Shared Preferences"),(0,a.mdx)("li",{parentName:"ul"},"Crash Reporter"),(0,a.mdx)("li",{parentName:"ul"},"React DevTools"),(0,a.mdx)("li",{parentName:"ul"},"Metro Logs")),(0,a.mdx)("p",null,"Additional plugins can be installed through the Plugin Manager."),(0,a.mdx)("p",null,"To create your own plugins and integrate with Flipper using JavaScript, see the ",(0,a.mdx)(o.default,{to:(0,l.default)("/docs/tutorial/react-native"),mdxType:"Link"},"Building a React Native Plugin")," tutorial."),(0,a.mdx)("h3",{id:"using-the-latest-flipper-sdk"},"Using the latest Flipper SDK"),(0,a.mdx)("p",null,"By default, React Native might ship with an outdated Flipper SDK. To make sure you are using the latest version, determine the latest released version of Flipper by running ",(0,a.mdx)("inlineCode",{parentName:"p"},"npm info flipper"),"."),(0,a.mdx)("p",null,"Latest version of Flipper requires react-native 0.69+! If you use react-native < 0.69.0, please, downgrade ",(0,a.mdx)("inlineCode",{parentName:"p"},"react-native-flipper")," to 0.162.0 (see ",(0,a.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper/issues/4240"},"this GitHub issue")," for details)."),(0,a.mdx)("p",null,"Android:"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Bump the ",(0,a.mdx)("inlineCode",{parentName:"li"},"FLIPPER_VERSION")," variable in ",(0,a.mdx)("inlineCode",{parentName:"li"},"android/gradle.properties"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},"FLIPPER_VERSION=0.259.0"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"./gradlew clean")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"android")," directory.")),(0,a.mdx)("p",null,"iOS:"),(0,a.mdx)("p",null,"react-native version => 0.69.0"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Call ",(0,a.mdx)("inlineCode",{parentName:"li"},"FlipperConfiguration.enabled")," with a specific version in ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios/Podfile"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},":flipper_configuration => FlipperConfiguration.enabled([\"Debug\"], { 'Flipper' => '0.190.0' }),"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install --repo-update")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory.")),(0,a.mdx)("p",null,"react-native version < 0.69.0"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Call ",(0,a.mdx)("inlineCode",{parentName:"li"},"use_flipper")," with a specific version in ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios/Podfile"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},"use_flipper!({ 'Flipper' => '0.259.0' })"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install --repo-update")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory.")),(0,a.mdx)("h2",{id:"manual-setup"},"Manual Setup"),(0,a.mdx)("p",null,"If you are not using a default React Native template or cannot use the upgrade tool, you can find instructions for how to integrate Flipper into your projects in the following guides:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("a",{href:(0,l.default)("/docs/getting-started/react-native-android")},"React Native for Android")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("a",{href:(0,l.default)("/docs/getting-started/react-native-ios")},"React Native for iOS"))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[880],{3905:(e,t,n)=>{n.r(t),n.d(t,{MDXContext:()=>d,MDXProvider:()=>s,mdx:()=>g,useMDXComponents:()=>m,withMDXComponents:()=>u});var i=n(67294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(){return a=Object.assign||function(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=i.createContext({}),u=function(e){return function(t){var n=m(t.components);return i.createElement(e,a({},t,{components:n}))}},m=function(e){var t=i.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},s=function(e){var t=m(e.components);return i.createElement(d.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},f=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,d=p(e,["components","mdxType","originalType","parentName"]),u=m(n),s=r,f=u["".concat(l,".").concat(s)]||u[s]||c[s]||a;return n?i.createElement(f,o(o({ref:t},d),{},{components:n})):i.createElement(f,o({ref:t},d))}));function g(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,l=new Array(a);l[0]=f;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o.mdxType="string"==typeof e?e:r,l[1]=o;for(var d=2;d{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>d,metadata:()=>m,toc:()=>c});var i=n(83117),r=n(80102),a=(n(67294),n(3905)),l=n(44996),o=n(39960),p=["components"],d={id:"react-native",title:"React Native App - Automatic Setup",sidebar_label:"Automatic Setup"},u=void 0,m={unversionedId:"getting-started/react-native",id:"getting-started/react-native",title:"React Native App - Automatic Setup",description:"Starting with React Native 0.62, after generating your project with react-native init, the Flipper integration is ready out of the box for debug builds:",source:"@site/../docs/getting-started/react-native.mdx",sourceDirName:"getting-started",slug:"/getting-started/react-native",permalink:"/docs/getting-started/react-native",draft:!1,editUrl:"https://github.com/facebook/flipper/blob/main/website/../docs/getting-started/react-native.mdx",tags:[],version:"current",frontMatter:{id:"react-native",title:"React Native App - Automatic Setup",sidebar_label:"Automatic Setup"},sidebar:"main",previous:{title:"Generic iOS Apps",permalink:"/docs/getting-started/ios-native"},next:{title:"Manual Android Setup",permalink:"/docs/getting-started/react-native-android"}},s={},c=[{value:"Using the latest Flipper SDK",id:"using-the-latest-flipper-sdk",level:3},{value:"Manual Setup",id:"manual-setup",level:2}],f={toc:c};function g(e){var t=e.components,n=(0,r.Z)(e,p);return(0,a.mdx)("wrapper",(0,i.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,a.mdx)("p",null,"Starting with React Native 0.62, after generating your project with ",(0,a.mdx)("inlineCode",{parentName:"p"},"react-native init"),", the Flipper integration is ready out of the box for debug builds:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("strong",{parentName:"li"},"Android")," - start the Flipper Desktop application and start your project using ",(0,a.mdx)("inlineCode",{parentName:"li"},"yarn android"),". Your application will appear in Flipper."),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("strong",{parentName:"li"},"iOS")," - run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install")," once in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory of your project. After that, run ",(0,a.mdx)("inlineCode",{parentName:"li"},"yarn ios")," and start Flipper. Your application will show up in Flipper.")),(0,a.mdx)("p",null,"By default, the following plugins will be available:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},"Layout Inspector"),(0,a.mdx)("li",{parentName:"ul"},"Network"),(0,a.mdx)("li",{parentName:"ul"},"Databases"),(0,a.mdx)("li",{parentName:"ul"},"Images"),(0,a.mdx)("li",{parentName:"ul"},"Shared Preferences"),(0,a.mdx)("li",{parentName:"ul"},"Crash Reporter"),(0,a.mdx)("li",{parentName:"ul"},"React DevTools"),(0,a.mdx)("li",{parentName:"ul"},"Metro Logs")),(0,a.mdx)("p",null,"Additional plugins can be installed through the Plugin Manager."),(0,a.mdx)("p",null,"To create your own plugins and integrate with Flipper using JavaScript, see the ",(0,a.mdx)(o.default,{to:(0,l.default)("/docs/tutorial/react-native"),mdxType:"Link"},"Building a React Native Plugin")," tutorial."),(0,a.mdx)("h3",{id:"using-the-latest-flipper-sdk"},"Using the latest Flipper SDK"),(0,a.mdx)("p",null,"By default, React Native might ship with an outdated Flipper SDK. To make sure you are using the latest version, determine the latest released version of Flipper by running ",(0,a.mdx)("inlineCode",{parentName:"p"},"npm info flipper"),"."),(0,a.mdx)("p",null,"Latest version of Flipper requires react-native 0.69+! If you use react-native < 0.69.0, please, downgrade ",(0,a.mdx)("inlineCode",{parentName:"p"},"react-native-flipper")," to 0.162.0 (see ",(0,a.mdx)("a",{parentName:"p",href:"https://github.com/facebook/flipper/issues/4240"},"this GitHub issue")," for details)."),(0,a.mdx)("p",null,"Android:"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Bump the ",(0,a.mdx)("inlineCode",{parentName:"li"},"FLIPPER_VERSION")," variable in ",(0,a.mdx)("inlineCode",{parentName:"li"},"android/gradle.properties"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},"FLIPPER_VERSION=0.260.0"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"./gradlew clean")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"android")," directory.")),(0,a.mdx)("p",null,"iOS:"),(0,a.mdx)("p",null,"react-native version => 0.69.0"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Call ",(0,a.mdx)("inlineCode",{parentName:"li"},"FlipperConfiguration.enabled")," with a specific version in ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios/Podfile"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},":flipper_configuration => FlipperConfiguration.enabled([\"Debug\"], { 'Flipper' => '0.190.0' }),"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install --repo-update")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory.")),(0,a.mdx)("p",null,"react-native version < 0.69.0"),(0,a.mdx)("ol",null,(0,a.mdx)("li",{parentName:"ol"},"Call ",(0,a.mdx)("inlineCode",{parentName:"li"},"use_flipper")," with a specific version in ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios/Podfile"),", for example: ",(0,a.mdx)("inlineCode",{parentName:"li"},"use_flipper!({ 'Flipper' => '0.260.0' })"),"."),(0,a.mdx)("li",{parentName:"ol"},"Run ",(0,a.mdx)("inlineCode",{parentName:"li"},"pod install --repo-update")," in the ",(0,a.mdx)("inlineCode",{parentName:"li"},"ios")," directory.")),(0,a.mdx)("h2",{id:"manual-setup"},"Manual Setup"),(0,a.mdx)("p",null,"If you are not using a default React Native template or cannot use the upgrade tool, you can find instructions for how to integrate Flipper into your projects in the following guides:"),(0,a.mdx)("ul",null,(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("a",{href:(0,l.default)("/docs/getting-started/react-native-android")},"React Native for Android")),(0,a.mdx)("li",{parentName:"ul"},(0,a.mdx)("a",{href:(0,l.default)("/docs/getting-started/react-native-ios")},"React Native for iOS"))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.67daed6d.js b/assets/js/runtime~main.22f4478a.js similarity index 93% rename from assets/js/runtime~main.67daed6d.js rename to assets/js/runtime~main.22f4478a.js index 59f354b8e91..ef46c5cbfe5 100644 --- a/assets/js/runtime~main.67daed6d.js +++ b/assets/js/runtime~main.22f4478a.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,c,f,d={},b={};function r(e){var a=b[e];if(void 0!==a)return a.exports;var c=b[e]={id:e,loaded:!1,exports:{}};return d[e].call(c.exports,c,c.exports,r),c.loaded=!0,c.exports}r.m=d,e=[],r.O=(a,c,f,d)=>{if(!c){var b=1/0;for(i=0;i=d)&&Object.keys(r.O).every((e=>r.O[e](c[o])))?c.splice(o--,1):(t=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[c,f,d]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var d=Object.create(null);r.r(d);var b={};a=a||[null,c({}),c([]),c(c)];for(var t=2&f&&e;"object"==typeof t&&!~a.indexOf(t);t=c(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(d,b),d},r.d=(e,a)=>{for(var c in a)r.o(a,c)&&!r.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,c)=>(r.f[c](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",141:"71b83891",247:"81e64fca",321:"5511adc2",359:"9fd1c838",361:"5c984d5c",384:"752c6bfa",428:"3a952f51",489:"74450489",504:"f15cd604",533:"b2b675dd",562:"f842f0be",577:"72eb3cfc",656:"e8fb12d3",795:"2c55addf",826:"c3cd9102",880:"fa2418a3",884:"885d314f",890:"1dfa6ca3",925:"b9a69694",972:"0fc226a0",1202:"c1a1ca59",1323:"c2a203b4",1334:"1bc0072c",1339:"4541de2f",1361:"5509b2c3",1396:"1649fa70",1477:"b2f554cd",1480:"871489d9",1602:"de22ccb4",1618:"d189d17c",1637:"2d0c6c37",1666:"6b8963f6",1699:"61ce1e27",1713:"a7023ddc",1841:"7121ab4b",1844:"6b849256",1901:"9c461a9c",1963:"eacc6f32",2093:"cc670dbc",2105:"ffc0ac08",2165:"dc9e5b6f",2255:"649adc27",2286:"413d47da",2293:"b0e9567c",2535:"814f3328",2593:"8e077482",2600:"a38292ce",2670:"935f56f3",2829:"618f754e",3089:"a6aa9e1f",3136:"329fe29b",3138:"9e36ac3e",3469:"6abc1ed2",3541:"3f3d2568",3608:"9e4087bc",3626:"bd3bbb8c",3742:"6d521c69",3844:"7de22887",3911:"5cfbc13a",4009:"88ab2182",4013:"01a85c17",4195:"c4f5d8e4",4200:"f092f9ad",4257:"41b472df",4321:"d6bf77a5",4332:"45292067",4457:"8a00a8a6",4509:"7c55db69",4556:"ad3dad21",4558:"fa4ba0b8",4572:"f8d921aa",4616:"e6910d4e",4665:"8443a6ca",4673:"219979e0",4723:"3adc86e1",4738:"45d29f2f",4923:"737d3d62",5008:"73e544a2",5081:"3b545665",5086:"96415b95",5095:"8b341be4",5096:"9bd3e6b8",5313:"2c15b752",5319:"486e75ec",5591:"5b707ace",5670:"1fc3a312",5805:"2a1d6d10",5869:"5d40bc6a",5891:"e7698892",5893:"b059735e",5920:"4e3f310d",6033:"303bd344",6103:"ccc49370",6119:"1678a047",6159:"89b0c4bc",6170:"877d3aa9",6190:"a37a44be",6228:"e1925c59",6350:"1377941e",6366:"a776b06d",6405:"6a95ba78",6466:"e45a8087",6700:"76d5d095",6711:"b48d5c42",6805:"129b6cd0",6908:"3e112228",6923:"11f2aa56",7001:"28820f38",7042:"e6085927",7092:"3a33985d",7233:"165b6557",7291:"711f1f5d",7298:"5927b9bc",7359:"f79f6a40",7582:"622de2b5",7636:"04f828f9",7713:"bce1d4a2",7718:"55cc260f",7857:"ffc92a56",7918:"17896441",7920:"1a4e3797",7941:"e3d1d3fe",8095:"b9913505",8134:"dee6a1e8",8284:"39c0cb82",8462:"2527d735",8464:"3432b9e5",8488:"859ec829",8610:"6875c492",9327:"50f6d426",9389:"3a5322a7",9510:"9f539a3c",9514:"1be78505",9558:"f933fdff",9782:"b6054734",9841:"df4cef3e",9848:"986f7180",9969:"99681e84",9977:"8c637ae2"}[e]||e)+"."+{53:"a5c1ded2",141:"141be83e",247:"4a5db342",321:"531e59e5",359:"26d65066",361:"3e5c7467",384:"5ef52ddf",428:"620f906d",489:"13b1c9cb",504:"eff84ee9",533:"21691e5f",562:"dc228255",577:"b24b9003",656:"029cbf43",795:"4d198965",826:"890a188b",880:"56863c34",884:"145814f7",890:"88069828",925:"8887c9c6",972:"61b1ee64",1202:"3334c30c",1323:"541a3cbb",1334:"3beedd88",1339:"46364c4f",1361:"1b75fb04",1396:"1e692d7d",1477:"c9147454",1480:"86f2dc24",1602:"380c270e",1618:"063f1f8b",1637:"38ddea98",1666:"16d2137e",1699:"8ca31dc7",1713:"4fbf67e1",1841:"2e147311",1844:"0c3b3598",1901:"8e30a8f8",1963:"13cf5f09",2093:"fd94e790",2105:"36ba8449",2165:"6d1923d4",2255:"32a0e61d",2286:"25f488be",2293:"c444beb7",2535:"15e90cff",2593:"647d3d89",2600:"1f540b0d",2670:"f6dcbcc5",2829:"dc266c5e",2964:"f872167c",3089:"c94f5caa",3136:"c484b243",3138:"4d5cff22",3469:"96175459",3541:"203d5bc1",3608:"3a46ad53",3626:"660aa315",3742:"03300823",3844:"e179f9b6",3911:"22c2f017",4009:"b7e3f624",4013:"e5060662",4089:"2a90eeb4",4195:"49a8d370",4200:"ab8080d5",4257:"b61f6090",4321:"c746795e",4332:"60ebcd16",4457:"b962ff73",4498:"6228baed",4509:"ed140fe7",4556:"3379a407",4558:"fbcf3e8a",4572:"fcd1e9e4",4616:"e7717698",4665:"dc523054",4673:"44f4f2d0",4723:"6b9d3e25",4738:"597de2c0",4923:"eabc66ce",4972:"02a734a6",5008:"a808ef79",5081:"afaf17a6",5086:"99d47390",5095:"75f2efe3",5096:"c1506472",5313:"ed24218a",5319:"8c9f48d6",5591:"deabd6a1",5670:"80042f4d",5805:"e077a879",5869:"24963c48",5891:"4c038a45",5893:"92d02f19",5920:"8a6a7bd2",6033:"e771e401",6103:"f49ab491",6119:"d3ff083c",6159:"0718cb96",6170:"ce254a02",6190:"7206509d",6228:"59e59921",6350:"e59c7a76",6366:"001ddeb6",6405:"45dcaef0",6466:"8a1d1756",6700:"6705152a",6711:"fcdb15b2",6780:"2366dc2b",6805:"e40add40",6908:"978c9a8c",6923:"93e2fd0f",6945:"96d36007",7001:"9bf4f8ed",7042:"1e92d391",7092:"db58d04e",7233:"cddcd02a",7291:"8f866aac",7298:"5030fb4b",7359:"fe241e90",7582:"0be6915e",7636:"4cb36ca8",7713:"043bbf4b",7718:"686fd04d",7857:"63c76b35",7918:"76bc5165",7920:"e4a7ee09",7941:"c0983fb5",7993:"91992245",8095:"20becfc1",8134:"1852ab01",8284:"7fd6419e",8462:"921eb45c",8464:"202d161f",8488:"bc61576a",8610:"55bb8e7f",8894:"ba661129",9327:"e9f60ba8",9389:"b0b6516f",9510:"8911653f",9514:"86b87b98",9558:"5834d7dd",9782:"0a155c35",9841:"3193f8cf",9848:"794e576f",9942:"38611243",9969:"21ed2625",9977:"ba8f316b"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},r.l=(e,a,c,d)=>{if(f[e])f[e].push(a);else{var b,t;if(void 0!==c)for(var o=document.getElementsByTagName("script"),n=0;n{b.onerror=b.onload=null,clearTimeout(u);var d=f[e];if(delete f[e],b.parentNode&&b.parentNode.removeChild(b),d&&d.forEach((e=>e(c))),a)return a(c)},u=setTimeout(l.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=l.bind(null,b.onerror),b.onload=l.bind(null,b.onload),t&&document.head.appendChild(b)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r.p="/",r.gca=function(e){return e={17896441:"7918",45292067:"4332",74450489:"489","935f2afb":"53","71b83891":"141","81e64fca":"247","5511adc2":"321","9fd1c838":"359","5c984d5c":"361","752c6bfa":"384","3a952f51":"428",f15cd604:"504",b2b675dd:"533",f842f0be:"562","72eb3cfc":"577",e8fb12d3:"656","2c55addf":"795",c3cd9102:"826",fa2418a3:"880","885d314f":"884","1dfa6ca3":"890",b9a69694:"925","0fc226a0":"972",c1a1ca59:"1202",c2a203b4:"1323","1bc0072c":"1334","4541de2f":"1339","5509b2c3":"1361","1649fa70":"1396",b2f554cd:"1477","871489d9":"1480",de22ccb4:"1602",d189d17c:"1618","2d0c6c37":"1637","6b8963f6":"1666","61ce1e27":"1699",a7023ddc:"1713","7121ab4b":"1841","6b849256":"1844","9c461a9c":"1901",eacc6f32:"1963",cc670dbc:"2093",ffc0ac08:"2105",dc9e5b6f:"2165","649adc27":"2255","413d47da":"2286",b0e9567c:"2293","814f3328":"2535","8e077482":"2593",a38292ce:"2600","935f56f3":"2670","618f754e":"2829",a6aa9e1f:"3089","329fe29b":"3136","9e36ac3e":"3138","6abc1ed2":"3469","3f3d2568":"3541","9e4087bc":"3608",bd3bbb8c:"3626","6d521c69":"3742","7de22887":"3844","5cfbc13a":"3911","88ab2182":"4009","01a85c17":"4013",c4f5d8e4:"4195",f092f9ad:"4200","41b472df":"4257",d6bf77a5:"4321","8a00a8a6":"4457","7c55db69":"4509",ad3dad21:"4556",fa4ba0b8:"4558",f8d921aa:"4572",e6910d4e:"4616","8443a6ca":"4665","219979e0":"4673","3adc86e1":"4723","45d29f2f":"4738","737d3d62":"4923","73e544a2":"5008","3b545665":"5081","96415b95":"5086","8b341be4":"5095","9bd3e6b8":"5096","2c15b752":"5313","486e75ec":"5319","5b707ace":"5591","1fc3a312":"5670","2a1d6d10":"5805","5d40bc6a":"5869",e7698892:"5891",b059735e:"5893","4e3f310d":"5920","303bd344":"6033",ccc49370:"6103","1678a047":"6119","89b0c4bc":"6159","877d3aa9":"6170",a37a44be:"6190",e1925c59:"6228","1377941e":"6350",a776b06d:"6366","6a95ba78":"6405",e45a8087:"6466","76d5d095":"6700",b48d5c42:"6711","129b6cd0":"6805","3e112228":"6908","11f2aa56":"6923","28820f38":"7001",e6085927:"7042","3a33985d":"7092","165b6557":"7233","711f1f5d":"7291","5927b9bc":"7298",f79f6a40:"7359","622de2b5":"7582","04f828f9":"7636",bce1d4a2:"7713","55cc260f":"7718",ffc92a56:"7857","1a4e3797":"7920",e3d1d3fe:"7941",b9913505:"8095",dee6a1e8:"8134","39c0cb82":"8284","2527d735":"8462","3432b9e5":"8464","859ec829":"8488","6875c492":"8610","50f6d426":"9327","3a5322a7":"9389","9f539a3c":"9510","1be78505":"9514",f933fdff:"9558",b6054734:"9782",df4cef3e:"9841","986f7180":"9848","99681e84":"9969","8c637ae2":"9977"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,c)=>{var f=r.o(e,a)?e[a]:void 0;if(0!==f)if(f)c.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var d=new Promise(((c,d)=>f=e[a]=[c,d]));c.push(f[2]=d);var b=r.p+r.u(a),t=new Error;r.l(b,(c=>{if(r.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var d=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;t.message="Loading chunk "+a+" failed.\n("+d+": "+b+")",t.name="ChunkLoadError",t.type=d,t.request=b,f[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,c)=>{var f,d,[b,t,o]=c,n=0;if(b.some((a=>0!==e[a]))){for(f in t)r.o(t,f)&&(r.m[f]=t[f]);if(o)var i=o(r)}for(a&&a(c);n{"use strict";var e,a,c,f,d={},b={};function r(e){var a=b[e];if(void 0!==a)return a.exports;var c=b[e]={id:e,loaded:!1,exports:{}};return d[e].call(c.exports,c,c.exports,r),c.loaded=!0,c.exports}r.m=d,e=[],r.O=(a,c,f,d)=>{if(!c){var b=1/0;for(i=0;i=d)&&Object.keys(r.O).every((e=>r.O[e](c[o])))?c.splice(o--,1):(t=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[c,f,d]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var d=Object.create(null);r.r(d);var b={};a=a||[null,c({}),c([]),c(c)];for(var t=2&f&&e;"object"==typeof t&&!~a.indexOf(t);t=c(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(d,b),d},r.d=(e,a)=>{for(var c in a)r.o(a,c)&&!r.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,c)=>(r.f[c](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",141:"71b83891",247:"81e64fca",321:"5511adc2",359:"9fd1c838",361:"5c984d5c",384:"752c6bfa",428:"3a952f51",489:"74450489",504:"f15cd604",533:"b2b675dd",562:"f842f0be",577:"72eb3cfc",656:"e8fb12d3",795:"2c55addf",826:"c3cd9102",880:"fa2418a3",884:"885d314f",890:"1dfa6ca3",925:"b9a69694",972:"0fc226a0",1202:"c1a1ca59",1323:"c2a203b4",1334:"1bc0072c",1339:"4541de2f",1361:"5509b2c3",1396:"1649fa70",1477:"b2f554cd",1480:"871489d9",1602:"de22ccb4",1618:"d189d17c",1637:"2d0c6c37",1666:"6b8963f6",1699:"61ce1e27",1713:"a7023ddc",1841:"7121ab4b",1844:"6b849256",1901:"9c461a9c",1963:"eacc6f32",2093:"cc670dbc",2105:"ffc0ac08",2165:"dc9e5b6f",2255:"649adc27",2286:"413d47da",2293:"b0e9567c",2535:"814f3328",2593:"8e077482",2600:"a38292ce",2670:"935f56f3",2829:"618f754e",3089:"a6aa9e1f",3136:"329fe29b",3138:"9e36ac3e",3469:"6abc1ed2",3541:"3f3d2568",3608:"9e4087bc",3626:"bd3bbb8c",3742:"6d521c69",3844:"7de22887",3911:"5cfbc13a",4009:"88ab2182",4013:"01a85c17",4195:"c4f5d8e4",4200:"f092f9ad",4257:"41b472df",4321:"d6bf77a5",4332:"45292067",4457:"8a00a8a6",4509:"7c55db69",4556:"ad3dad21",4558:"fa4ba0b8",4572:"f8d921aa",4616:"e6910d4e",4665:"8443a6ca",4673:"219979e0",4723:"3adc86e1",4738:"45d29f2f",4923:"737d3d62",5008:"73e544a2",5081:"3b545665",5086:"96415b95",5095:"8b341be4",5096:"9bd3e6b8",5313:"2c15b752",5319:"486e75ec",5591:"5b707ace",5670:"1fc3a312",5805:"2a1d6d10",5869:"5d40bc6a",5891:"e7698892",5893:"b059735e",5920:"4e3f310d",6033:"303bd344",6103:"ccc49370",6119:"1678a047",6159:"89b0c4bc",6170:"877d3aa9",6190:"a37a44be",6228:"e1925c59",6350:"1377941e",6366:"a776b06d",6405:"6a95ba78",6466:"e45a8087",6700:"76d5d095",6711:"b48d5c42",6805:"129b6cd0",6908:"3e112228",6923:"11f2aa56",7001:"28820f38",7042:"e6085927",7092:"3a33985d",7233:"165b6557",7291:"711f1f5d",7298:"5927b9bc",7359:"f79f6a40",7582:"622de2b5",7636:"04f828f9",7713:"bce1d4a2",7718:"55cc260f",7857:"ffc92a56",7918:"17896441",7920:"1a4e3797",7941:"e3d1d3fe",8095:"b9913505",8134:"dee6a1e8",8284:"39c0cb82",8462:"2527d735",8464:"3432b9e5",8488:"859ec829",8610:"6875c492",9327:"50f6d426",9389:"3a5322a7",9510:"9f539a3c",9514:"1be78505",9558:"f933fdff",9782:"b6054734",9841:"df4cef3e",9848:"986f7180",9969:"99681e84",9977:"8c637ae2"}[e]||e)+"."+{53:"a5c1ded2",141:"141be83e",247:"4a5db342",321:"531e59e5",359:"26d65066",361:"3e5c7467",384:"5ef52ddf",428:"c7de4763",489:"13b1c9cb",504:"eff84ee9",533:"21691e5f",562:"dc228255",577:"b24b9003",656:"029cbf43",795:"4d198965",826:"890a188b",880:"55c7f797",884:"145814f7",890:"88069828",925:"8887c9c6",972:"1a542511",1202:"3334c30c",1323:"541a3cbb",1334:"3beedd88",1339:"3f777449",1361:"1b75fb04",1396:"1e692d7d",1477:"c9147454",1480:"86f2dc24",1602:"380c270e",1618:"063f1f8b",1637:"38ddea98",1666:"16d2137e",1699:"d16b23c2",1713:"4fbf67e1",1841:"2e147311",1844:"0c3b3598",1901:"8e30a8f8",1963:"13cf5f09",2093:"fd94e790",2105:"36ba8449",2165:"86af65ec",2255:"32a0e61d",2286:"25f488be",2293:"c444beb7",2535:"15e90cff",2593:"647d3d89",2600:"1f540b0d",2670:"f6dcbcc5",2829:"dc266c5e",2964:"f872167c",3089:"c94f5caa",3136:"c484b243",3138:"4d5cff22",3469:"96175459",3541:"ef89c82f",3608:"3a46ad53",3626:"660aa315",3742:"03300823",3844:"e179f9b6",3911:"22c2f017",4009:"b7e3f624",4013:"e5060662",4089:"2a90eeb4",4195:"49a8d370",4200:"ab8080d5",4257:"b61f6090",4321:"c746795e",4332:"60ebcd16",4457:"b962ff73",4498:"6228baed",4509:"ed140fe7",4556:"3379a407",4558:"fbcf3e8a",4572:"fcd1e9e4",4616:"e7717698",4665:"dc523054",4673:"44f4f2d0",4723:"6b9d3e25",4738:"597de2c0",4923:"eabc66ce",4972:"02a734a6",5008:"a808ef79",5081:"afaf17a6",5086:"99d47390",5095:"75f2efe3",5096:"c1506472",5313:"ed24218a",5319:"8c9f48d6",5591:"deabd6a1",5670:"80042f4d",5805:"e077a879",5869:"24963c48",5891:"4c038a45",5893:"92d02f19",5920:"8a6a7bd2",6033:"9a474805",6103:"f49ab491",6119:"d3ff083c",6159:"0718cb96",6170:"ce254a02",6190:"5fe16789",6228:"59e59921",6350:"e59c7a76",6366:"001ddeb6",6405:"45dcaef0",6466:"8a1d1756",6700:"6705152a",6711:"fcdb15b2",6780:"2366dc2b",6805:"e40add40",6908:"978c9a8c",6923:"93e2fd0f",6945:"96d36007",7001:"9bf4f8ed",7042:"1e92d391",7092:"db58d04e",7233:"cddcd02a",7291:"8f866aac",7298:"5030fb4b",7359:"fe241e90",7582:"0be6915e",7636:"4cb36ca8",7713:"043bbf4b",7718:"686fd04d",7857:"63c76b35",7918:"76bc5165",7920:"e4a7ee09",7941:"c0983fb5",7993:"91992245",8095:"20becfc1",8134:"1852ab01",8284:"7fd6419e",8462:"921eb45c",8464:"202d161f",8488:"bc61576a",8610:"55bb8e7f",8894:"ba661129",9327:"e9f60ba8",9389:"b0b6516f",9510:"8911653f",9514:"86b87b98",9558:"5834d7dd",9782:"0a155c35",9841:"3193f8cf",9848:"794e576f",9942:"38611243",9969:"21ed2625",9977:"ba8f316b"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},r.l=(e,a,c,d)=>{if(f[e])f[e].push(a);else{var b,t;if(void 0!==c)for(var o=document.getElementsByTagName("script"),n=0;n{b.onerror=b.onload=null,clearTimeout(u);var d=f[e];if(delete f[e],b.parentNode&&b.parentNode.removeChild(b),d&&d.forEach((e=>e(c))),a)return a(c)},u=setTimeout(l.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=l.bind(null,b.onerror),b.onload=l.bind(null,b.onload),t&&document.head.appendChild(b)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r.p="/",r.gca=function(e){return e={17896441:"7918",45292067:"4332",74450489:"489","935f2afb":"53","71b83891":"141","81e64fca":"247","5511adc2":"321","9fd1c838":"359","5c984d5c":"361","752c6bfa":"384","3a952f51":"428",f15cd604:"504",b2b675dd:"533",f842f0be:"562","72eb3cfc":"577",e8fb12d3:"656","2c55addf":"795",c3cd9102:"826",fa2418a3:"880","885d314f":"884","1dfa6ca3":"890",b9a69694:"925","0fc226a0":"972",c1a1ca59:"1202",c2a203b4:"1323","1bc0072c":"1334","4541de2f":"1339","5509b2c3":"1361","1649fa70":"1396",b2f554cd:"1477","871489d9":"1480",de22ccb4:"1602",d189d17c:"1618","2d0c6c37":"1637","6b8963f6":"1666","61ce1e27":"1699",a7023ddc:"1713","7121ab4b":"1841","6b849256":"1844","9c461a9c":"1901",eacc6f32:"1963",cc670dbc:"2093",ffc0ac08:"2105",dc9e5b6f:"2165","649adc27":"2255","413d47da":"2286",b0e9567c:"2293","814f3328":"2535","8e077482":"2593",a38292ce:"2600","935f56f3":"2670","618f754e":"2829",a6aa9e1f:"3089","329fe29b":"3136","9e36ac3e":"3138","6abc1ed2":"3469","3f3d2568":"3541","9e4087bc":"3608",bd3bbb8c:"3626","6d521c69":"3742","7de22887":"3844","5cfbc13a":"3911","88ab2182":"4009","01a85c17":"4013",c4f5d8e4:"4195",f092f9ad:"4200","41b472df":"4257",d6bf77a5:"4321","8a00a8a6":"4457","7c55db69":"4509",ad3dad21:"4556",fa4ba0b8:"4558",f8d921aa:"4572",e6910d4e:"4616","8443a6ca":"4665","219979e0":"4673","3adc86e1":"4723","45d29f2f":"4738","737d3d62":"4923","73e544a2":"5008","3b545665":"5081","96415b95":"5086","8b341be4":"5095","9bd3e6b8":"5096","2c15b752":"5313","486e75ec":"5319","5b707ace":"5591","1fc3a312":"5670","2a1d6d10":"5805","5d40bc6a":"5869",e7698892:"5891",b059735e:"5893","4e3f310d":"5920","303bd344":"6033",ccc49370:"6103","1678a047":"6119","89b0c4bc":"6159","877d3aa9":"6170",a37a44be:"6190",e1925c59:"6228","1377941e":"6350",a776b06d:"6366","6a95ba78":"6405",e45a8087:"6466","76d5d095":"6700",b48d5c42:"6711","129b6cd0":"6805","3e112228":"6908","11f2aa56":"6923","28820f38":"7001",e6085927:"7042","3a33985d":"7092","165b6557":"7233","711f1f5d":"7291","5927b9bc":"7298",f79f6a40:"7359","622de2b5":"7582","04f828f9":"7636",bce1d4a2:"7713","55cc260f":"7718",ffc92a56:"7857","1a4e3797":"7920",e3d1d3fe:"7941",b9913505:"8095",dee6a1e8:"8134","39c0cb82":"8284","2527d735":"8462","3432b9e5":"8464","859ec829":"8488","6875c492":"8610","50f6d426":"9327","3a5322a7":"9389","9f539a3c":"9510","1be78505":"9514",f933fdff:"9558",b6054734:"9782",df4cef3e:"9841","986f7180":"9848","99681e84":"9969","8c637ae2":"9977"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,c)=>{var f=r.o(e,a)?e[a]:void 0;if(0!==f)if(f)c.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var d=new Promise(((c,d)=>f=e[a]=[c,d]));c.push(f[2]=d);var b=r.p+r.u(a),t=new Error;r.l(b,(c=>{if(r.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var d=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;t.message="Loading chunk "+a+" failed.\n("+d+": "+b+")",t.name="ChunkLoadError",t.type=d,t.request=b,f[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,c)=>{var f,d,[b,t,o]=c,n=0;if(b.some((a=>0!==e[a]))){for(f in t)r.o(t,f)&&(r.m[f]=t[f]);if(o)var i=o(r)}for(a&&a(c);n - + @@ -35,7 +35,7 @@ Contributing changes should be as simple as cloning the repository and running yarn && yarn start in the desktop/ folder.

Investing in debugging tools, both generic ones or just for specific apps, will benefit iteration speed. And we hope Flipper will make it as hassle free as possible to create your debugging tools. For an overview of Flipper for React Native, and why and how to build your own plugins, we recommend checking out the Flipper: The Extensible DevTool Platform for React Native talk.

Happy debugging!

- + \ No newline at end of file diff --git a/blog/2022/02/21/js-flipper-announcement/index.html b/blog/2022/02/21/js-flipper-announcement/index.html index 209fd5605b8..48ab7341fdf 100644 --- a/blog/2022/02/21/js-flipper-announcement/index.html +++ b/blog/2022/02/21/js-flipper-announcement/index.html @@ -17,7 +17,7 @@ - + @@ -123,7 +123,7 @@ Native, and why and how to build your own plugins, we recommend checking out the Flipper: The Extensible DevTool Platform for React Native talk.

Happy debugging!

- + \ No newline at end of file diff --git a/blog/2022/05/20/preparing-for-headless-flipper/index.html b/blog/2022/05/20/preparing-for-headless-flipper/index.html index 120c9283ae2..65d24603cf6 100644 --- a/blog/2022/05/20/preparing-for-headless-flipper/index.html +++ b/blog/2022/05/20/preparing-for-headless-flipper/index.html @@ -17,7 +17,7 @@ - + @@ -101,7 +101,7 @@ Native, and why and how to build your own plugins, we recommend checking out the Flipper: The Extensible DevTool Platform for React Native talk.

Happy debugging!

- + \ No newline at end of file diff --git a/blog/archive/index.html b/blog/archive/index.html index ea0650885b3..979c591343b 100644 --- a/blog/archive/index.html +++ b/blog/archive/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html index eca901d1172..73549832676 100644 --- a/blog/index.html +++ b/blog/index.html @@ -17,7 +17,7 @@ - + @@ -41,7 +41,7 @@ JavaScript clients. Without further ado, welcome js-flipper!

In this post we will:

  • See what js-flipper is
  • Get acquainted with how to build a Flipper plugin for a React app
  • Learn how Flipper talks to a mobile device
  • Dive deeper into the message structure
  • Glance at what it takes to support a new platform

· 4 min read
Michel Weststrate

Over the last year, the Flipper adoption in the open source community has doubled, and for many React Native developers Flipper has become the default debugging tool. As the community continues to grow, we believe it’s important to share updates about our plans.

- + \ No newline at end of file diff --git a/blog/tags/flipper/index.html b/blog/tags/flipper/index.html index 58b5a5e7efe..a09e27a23c9 100644 --- a/blog/tags/flipper/index.html +++ b/blog/tags/flipper/index.html @@ -17,7 +17,7 @@ - + @@ -41,7 +41,7 @@ JavaScript clients. Without further ado, welcome js-flipper!

In this post we will:

  • See what js-flipper is
  • Get acquainted with how to build a Flipper plugin for a React app
  • Learn how Flipper talks to a mobile device
  • Dive deeper into the message structure
  • Glance at what it takes to support a new platform

· 4 min read
Michel Weststrate

Over the last year, the Flipper adoption in the open source community has doubled, and for many React Native developers Flipper has become the default debugging tool. As the community continues to grow, we believe it’s important to share updates about our plans.

- + \ No newline at end of file diff --git a/blog/tags/headless/index.html b/blog/tags/headless/index.html index d136c9a606b..9ba43af7366 100644 --- a/blog/tags/headless/index.html +++ b/blog/tags/headless/index.html @@ -17,7 +17,7 @@ - + @@ -35,7 +35,7 @@ else that runs Flipper Client) from your terminal. Imagine deploying Flipper remotely in the cloud and interacting with it from your laptop. Imagine using your favorite plugins for automated testing.

In this post we cover:

  • How Flipper changes to facilitate the headless mode
  • How it affects plugins
  • A migration guide
- + \ No newline at end of file diff --git a/blog/tags/index.html b/blog/tags/index.html index 77b70cd8106..c96fa5f45c7 100644 --- a/blog/tags/index.html +++ b/blog/tags/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/blog/tags/node-js/index.html b/blog/tags/node-js/index.html index 33bcf10821b..c9453dedbc4 100644 --- a/blog/tags/node-js/index.html +++ b/blog/tags/node-js/index.html @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ WebSockets, we have committed to providing an official documented SDK for JavaScript clients. Without further ado, welcome js-flipper!

In this post we will:

  • See what js-flipper is
  • Get acquainted with how to build a Flipper plugin for a React app
  • Learn how Flipper talks to a mobile device
  • Dive deeper into the message structure
  • Glance at what it takes to support a new platform
- + \ No newline at end of file diff --git a/blog/tags/plugins/index.html b/blog/tags/plugins/index.html index 734abd1d8d5..5c296b371d6 100644 --- a/blog/tags/plugins/index.html +++ b/blog/tags/plugins/index.html @@ -17,7 +17,7 @@ - + @@ -35,7 +35,7 @@ else that runs Flipper Client) from your terminal. Imagine deploying Flipper remotely in the cloud and interacting with it from your laptop. Imagine using your favorite plugins for automated testing.

In this post we cover:

  • How Flipper changes to facilitate the headless mode
  • How it affects plugins
  • A migration guide
- + \ No newline at end of file diff --git a/blog/tags/react-native/index.html b/blog/tags/react-native/index.html index 60bf7b722ce..0a2c63dec77 100644 --- a/blog/tags/react-native/index.html +++ b/blog/tags/react-native/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

One post tagged with "react-native"

View All Tags

· 4 min read
Michel Weststrate

Over the last year, the Flipper adoption in the open source community has doubled, and for many React Native developers Flipper has become the default debugging tool. As the community continues to grow, we believe it’s important to share updates about our plans.

- + \ No newline at end of file diff --git a/blog/tags/react/index.html b/blog/tags/react/index.html index a1bd855d401..e9068e9d0e0 100644 --- a/blog/tags/react/index.html +++ b/blog/tags/react/index.html @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ WebSockets, we have committed to providing an official documented SDK for JavaScript clients. Without further ado, welcome js-flipper!

In this post we will:

  • See what js-flipper is
  • Get acquainted with how to build a Flipper plugin for a React app
  • Learn how Flipper talks to a mobile device
  • Dive deeper into the message structure
  • Glance at what it takes to support a new platform
- + \ No newline at end of file diff --git a/blog/tags/web/index.html b/blog/tags/web/index.html index 9e102c31dcc..4646ec599bc 100644 --- a/blog/tags/web/index.html +++ b/blog/tags/web/index.html @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ WebSockets, we have committed to providing an official documented SDK for JavaScript clients. Without further ado, welcome js-flipper!

In this post we will:

  • See what js-flipper is
  • Get acquainted with how to build a Flipper plugin for a React app
  • Learn how Flipper talks to a mobile device
  • Dive deeper into the message structure
  • Glance at what it takes to support a new platform
- + \ No newline at end of file diff --git a/docs/custom-ports/index.html b/docs/custom-ports/index.html index 176d67acaf7..a0649e942e7 100644 --- a/docs/custom-ports/index.html +++ b/docs/custom-ports/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Running Flipper with Custom Ports

Flipper ports - mobile apps that support certificate exchange

info

By default, Flipper runs its servers on ports 9088 and 9089. The mobile SDKs look for servers on those ports.

Each of these can be overridden by setting an environment variable, with the format: ${INSECURE_PORT},${SECURE_PORT}.

To run the desktop app using custom ports, use the following:

env FLIPPER_PORTS=1111,2222 ./flipper

or with a dev build:

env FLIPPER_PORTS=1111,2222 yarn start

Android SDK

To configure the Android SDK for custom ports, set the flipper.ports prop to your chosen ports 1111,2222 and then launch the Android app:

adb shell su 0 setprop flipper.ports 1111,2222

iOS SDK

To configure the iOS SDK for custom ports, set the FLIPPER_PORTS environment variable in your app launch script or set it system-wide through User Defaults:

xcrun simctl spawn booted defaults write "Apple Global Domain" "com.facebook.flipper.ports" -string "1111,2222"

Flipper ports - apps that do not support certificate exchange

rmation

Flipper listens on port 8333 for unsecure (browser, Node.js) connections.

To run the desktop app using custom ports, use the following:

env FLIPPER_BROWSER_PORT=1111 ./flipper

or with a dev build:

env FLIPPER_BROWSER_PORT=1111 yarn start

To connect to Flipper on a different port from js-flipper, you need to override its urlBase when you start it.

flipperClient.start('React Tic-Tac-Toe', { urlBase: 'localhost:1111' });

Metro Server Ports

You can also setup Flipper to use a different Metro Server port (default=8081) using this environement variable, as follows:

METRO_SERVER_PORT=3333 ./flipper

ADB Reverse Proxy ports

You can setup a different ADB port, used for reverse proxying when plugged through USB (default=5037), using the following:

ANDROID_ADB_SERVER_PORT=4444 ./flipper
- + \ No newline at end of file diff --git a/docs/extending/arch/index.html b/docs/extending/arch/index.html index 5ebdeb90feb..edc9f94fc28 100644 --- a/docs/extending/arch/index.html +++ b/docs/extending/arch/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Architecture

Flipper is built to be a universal pluggable platform for development tools. Currently, Flipper focuses on Android and iOS development, but its design does not limit it to these platforms. Another way to think of Flipper is a more general-purpose implementation of Chrome DevTools.

Overview

Flipper is a Web Application which consists of a Browser interface built with HTML/CSS/JS on top of Node.JS HTTP server so that it can be packaged to run on any operating system.

This desktop app connects over a TCP connection to applications running on simulators and connected devices. An application running on a device or simulator is called a 'client'.

The connection is bi-directional, allowing the desktop to query information from the client as well allowing the client to push updates directly to the desktop.

By querying data and responding to pushing from the client, a Flipper plugin is able to visualize data, debug problems, and change the behavior of running applications. Flipper provides the platform to build these tools and does not limit what kind of tools that may be.

There are two types of plugins in Flipper:

  • Client plugins - expose information as an API to desktop plugins whose responsibility it is to render this information in an easy-to-digest way. Client plugins are written once for each platform in the platform's native language.
  • Desktop plugins - written only once in JavaScript using React and consume the APIs exposed by the client plugins.

Architecture - React Native

The following diagram shows a simplified visualization of the Flipper architecture when used with React Native.

Flipper communication diagram
- + \ No newline at end of file diff --git a/docs/extending/client-plugin-lifecycle/index.html b/docs/extending/client-plugin-lifecycle/index.html index 6f4df872776..b4579103d7c 100644 --- a/docs/extending/client-plugin-lifecycle/index.html +++ b/docs/extending/client-plugin-lifecycle/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Client Plugin Lifecycle

There are two types of client plugin: Regular and Background. It's recommended you start off with a regular plugin and switch to a background plugin if necessary.

For both types of plugin, it's recommended you start work after the onConnect is called then terminate work after onDisconnect, when possible. This prevents wasted computation when Flipper isn't connected. If the plugin needs to keep track of events that occur before it gets connected (such as initial network requests on app startup), you should do so in the plugin constructor (or ideally in a separate class).

Regular plugin Lifecycle

For regular plugins, onConnect and onDisconnect are triggered when the user opens the plugin in the Flipper UI, and when they switch to another plugin, respectively. The process is illustrated in the following diagram.

Regular Plugin Lifecycle diagram

Background Plugin Lifecycle

For background plugins, onConnect is called when Flipper first connects, and onDisconnect when it disconnects. The user does not need to be viewing the plugin for it to send messages; they will be queued up until the next time the user opens the plugin where they will be processed.

Even for background plugins, onDisconnect and onConnect may be called on a plugin (such as if the user restarts Flipper). Plugins should handle this accordingly by making sure to resend any important data to the reconnected instance. The process is illustrated in the following diagram.

Background Plugin Lifecycle diagram
danger

Note that a plugin must be enabled by the user for its messages to be queued up.

- + \ No newline at end of file diff --git a/docs/extending/create-plugin/index.html b/docs/extending/create-plugin/index.html index c5bd21a16e6..c9e9dc160e9 100644 --- a/docs/extending/create-plugin/index.html +++ b/docs/extending/create-plugin/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Client Plugin API

FlipperPlugin

The plugin implementation that runs on (mobile) applications is called the client plugin in Flipper terminology.

To build a client plugin, implement the FlipperPlugin interface.

The ID that is returned from your implementation needs to match the name defined in your JavaScript counterpart's package.json.

public class MyFlipperPlugin implements FlipperPlugin {
private FlipperConnection mConnection;

@Override
public String getId() {
return "MyFlipperPlugin";
}

@Override
public void onConnect(FlipperConnection connection) throws Exception {
mConnection = connection;
}

@Override
public void onDisconnect() throws Exception {
mConnection = null;
}

@Override
public boolean runInBackground() {
return false;
}
}

Using FlipperConnection

onConnect will be called when your plugin becomes active. This will provide a FlipperConnection allowing you to register receivers for desktop method calls and respond with data.

connection.receive("getData", new FlipperReceiver() {
@Override
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
responder.success(
new FlipperObject.Builder()
.put("data", MyData.get())
.build());
}
});

Push data to the desktop

You don't have to wait for the desktop to request data. You can also push data directly to the desktop. If the JS plugin subscribes to the same method, it will receive the data.

connection.send("MyMessage",
new FlipperObject.Builder()
.put("message", "Hello")
.build()

Using a plugin instance to send data

It is often useful to get an instance of a Flipper plugin to send data to it: Flipper makes this simple with built-in support.

using FlipperClient to obtain a plugin instance

Plugins should be treated as singleton instances as there can only be one FlipperClient and each FlipperClient can only have one instance of a certain plugin. The Flipper API makes this simple by offering a way to get the current client and query it for plugins.

Plugins are identified by the string that their identifier method returns, in this example, 'MyFlipperPlugin'.

note

Null checks may be required as plugins may not be initialized, such as in production builds.

final FlipperClient client = AndroidFlipperClient.getInstanceIfInitialized(context);
if (client != null) {
final MyFlipperPlugin plugin = client.getPluginByClass(MyFlipperPlugin.class);
plugin.sendData(myData);
}

In the above snippet, sendData is an example of a method that might be implemented by the Flipper plugin.

Bi-directional communication demo

A minimal communication demo for Android and iOS can be found in the 'Sample' project:

For React Native and JavaScript, there is a simple game of Tic Tac Toe:

Background plugins

In some cases, you may want to provide data to Flipper even when your plugin is not currently active. Returning true in runInBackground() results in onConnect being called as soon as Flipper connects, which enables you to use the connection at any time. For more detals, see the Client Plugin Lifecycle. The advantage of this method is that the desktop plugin can process this data in the background and fire notifications. It also reduces the number of renders and time taken to display the data when the plugin becomes active.

danger

Please note that a background plugin could keep some data in memory until a Flipper connection is available, such as to keep statistics about the app startup process. However, a plugin shouldn't assume it will eventually get a connection, since this depends on whether the user has enabled the plugin on the Desktop side.

It's important to make sure that unbounded amounts of data are not stored!

- + \ No newline at end of file diff --git a/docs/extending/debugging/index.html b/docs/extending/debugging/index.html index 40c4821bfa3..8c398e8ac42 100644 --- a/docs/extending/debugging/index.html +++ b/docs/extending/debugging/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Debugging

Built-in Developer Tools

Flipper runs in the Browser. This means you can debug Flipper using any web developer tools like Chrome's Developer Tools.

In addition, to assist you with the JavaScript, the JS console displays uncaught exceptions thrown from the client plugin in response to Flipper method calls.

Google Chrome Developer Tools

As an alternative to using built-in Developer Tools, you can also attach to the running Flipper instance from Google Chrome Developer Tools. Sometimes this is useful because the version of Dev Tools in Google Chrome is more recent than the embedded one, and it is possible to easily install additional extensions if required.

To attach the running Flipper instance, open a new tab in Google Chrome, navigate to http://localhost:9222 and choose 'Flipper' in the opened list, as shown in the following screenshot

Attach From Google Chrome

Visual Studio Code

If you prefer to use the same editor for both coding and debugging, you can attach to the running Flipper instance for debugging right from Visual Studio Code. Take the following steps:

  • First, you need to install the extension 'Debugger for Chrome'.
  • To start debugging, open the folder desktop of the Flipper repository in VSCode
  • Execute yarn start in a terminal to launch Flipper in development mode
  • Select tab Debug and Run and run task Attach to Running Renderer. By default, this task is set as the first one, so you can just press F5 to run it.
Attach From Visual Studio Code

Plugin missing

If a plugin you've created is not showing up, there might be two potential classes of problems, either there is a problem on the mobile side or on the desktop side (understanding where the problem is rooted helps in debugging it).

Click on 'Plugin now showing' in the sidebar and see if your plugin is now listed. If it is not listed, the desktop side of the plugin is not loaded. One of the main reasons for this is that the plugin could not be compiled, due to some errors. Try launching Flipper from the Terminal to see some additional logs: /Applications/Flipper.app/Contents/MacOS/Flipper.

A common error here is Error: fsevents unavailable (this watcher can only be used on Darwin). This can be fixed by installing watchman (brew install watchman).

Add console.log("test phrase %", object) and then search the developer tools log in Flipper to find what the value of the object is. If the plugin is listed in the desktop app, but still is not showing up in the sidebar, the mobile app is not announcing the plugin. In this case, make sure to instantiate your plugin and add it to your FlipperClient.

- + \ No newline at end of file diff --git a/docs/extending/deeplinks/index.html b/docs/extending/deeplinks/index.html index 6137baaf690..9dfc1fc8edd 100644 --- a/docs/extending/deeplinks/index.html +++ b/docs/extending/deeplinks/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Using Deeplinks

Flipper supports opening Flipper through deeplinks via the web+flipper:// protocol.

open-plugin

The following link format can be used to open Flipper and open a specific plugin:

web+flipper://open-plugin?plugin-id=<plugin-id>&client=<client>&devices=<devices>&payload=<payload>

The parameters are specified as follows:

  • plugin-id - [required] the identifier of the plugin that needs to be opened, as specified by the id field in package.json.
  • client - [optional] the name of the application that should be opened. For device plugins, this doesn't need to be specified. If not set, the user will be prompted to select a client.
  • devices - [optional] comma separated list of device types that are acceptable. For example, iOS,Android or Metro. If set, client and plugin-id must be running on this type of device.
  • payload - [optional] any additional string argument to pass to the plugin. Note that this argument should be properly URL encoded.

Using this deeplink format ensures that:

  • [FB-ONLY] The user is logged in & connected to Facebook.
  • Flipper is up to date.
  • [FB-ONLY] The specified plugin is installed.
  • The plugin is available on the provided client / device, or will prompt the user for a valid device / client selection is made on which the plugin is available.
  • The plugin is enabled.

If a plugin is opened through a deeplink, for which a payload was set, the onDeepLink handler will be triggered directly after initializing and rendering the plugin.

note

The same payload format can also be used to open other plugins programmatically from inside another plugin, by passing the payload as second argument to selectPlugin.

- + \ No newline at end of file diff --git a/docs/extending/desktop-plugin-structure/index.html b/docs/extending/desktop-plugin-structure/index.html index 459e74f7243..fdd46831336 100644 --- a/docs/extending/desktop-plugin-structure/index.html +++ b/docs/extending/desktop-plugin-structure/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Plugin structure

Flipper Desktop plugins have a rigid structure. It's recommended to scaffold any new plugin using the Flipper scaffolding tooling.

Scaffolding a new plugin

flipper-pkg

The CLI tool flipper-pkg helps to initialize, validate, and package Flipper desktop plugins.

To scaffold a new plugin run npx flipper-pkg init in the directory where you want to store the plugin sources. Note that this should typically not be inside a Flipper checkout, but rather a fresh directory which you can put under your own source control.

Desktop Plugin structure

All Flipper Desktop plugins must be self-contained in a directory that must contain, at a minimum:

  • package.json - the plugin packet manifest.
  • An entry source file for your plugin (such as src/index.tsx).

After scaffolding a new plugin has finished, those files will exist in the relevant directory.

An example package.json file could look like the following:

{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-myplugin",
"id": "myplugin",
"pluginType": "client",
"version": "1.0.0",
"main": "dist/bundle.js",
"flipperBundlerEntry": "src/index.tsx",
"license": "MIT",
"keywords": ["flipper-plugin"],
"title": "My Plugin",
"icon": "apps",
"bugs": {
"email": "you@example.com"
},
"scripts": {
"lint": "flipper-pkg lint",
"prepack": "flipper-pkg lint && flipper-pkg bundle"
},
"peerDependencies": {
"flipper": "latest",
"flipper-plugin": "latest"
},
"devDependencies": {
"flipper": "latest",
"flipper-plugin": "latest",
"flipper-pkg": "latest",
"react": "latest",
"antd": "latest"
}
}

The following are important attributes of package.json:

  • $schema - must contain the URI identifying scheme according to which the plugin is defined. Currently, Flipper supports plugins defined by the specification version 2, while version 1 is being deprecated.

  • name - the NPM package name. Should start with flipper-plugin- by convention so the Flipper plugins can be easily find it on NPM.

  • id - the plugin native identifier, which must match the mobile plugin identifier returned by the getId method of your Java plugin.

  • pluginType - Specifies the plugin type: client or device. For details, see the Anatomy of a Desktop plugin, below.

  • main - points to the plugin bundle which is loaded by Flipper. The "flipper-pkg" utility uses this field to determine output location during plugin bundling.

  • flipperBundlerEntry - points to the source entry point used for plugin code bundling. flipper-pkg takes the path specified in flipperBundlerEntry as source, transpiles and bundles it, and saves the output to the path specified in main.

  • keywords - the field must contain the flipper-plugin keyword, otherwise Flipper won't discover the plugin. Additionally, the field can also contain any other keywords for better plugin discoverability.

  • title - shown in the main sidebar as the human-readable name of the plugin.

  • icon - determines the plugin icon that is displayed in the main sidebar. The list of available icons is static for now (see icons.json in GitHub).

  • bugs - specify an email and/or URL where plugin bugs should be reported.

In index.tsx you define the plugin in JavaScript, as shown in the following example:

export function plugin(client) {
return {};
}

export function Component() {
return 'hello world';
}
note

Some public plugins use a FlipperPlugin base class. That format is deprecated.

Anatomy of a Desktop plugin

Flipper Desktop plugins come in three possible flavors:

  1. Client plugins - connects to a specific client plugin running in an app (recommended).
  2. Device plugins - doesn't connect to a specific client and doesn't have a native counterpart but shows data about the device obtained through some other means, like device logs, device temperatures, and so on.
  3. Table plugin - a simplified version of a client plugin that merely displays incoming data from a client plugin in a table.

Creating a Client Plugin

A plugin always exposes two elements from its entry module (typically src/index.tsx), plugin and Component, as shown in the following snippet:

import {PluginClient} from 'flipper-plugin';

export function plugin(client: PluginClient) {
return {}; // API exposed from this plugin
}

export function Component() {
// Plugin UI
return <h1>Welcome to my first plugin</h1>;
}

For details on how to write custom Flipper plugins, see the Building a Desktop Plugin - Custom UI tutorial page.

Creating a Device Plugin

Flipper also supports so-called device plugins (plugins that are available for an entire device) but don't receive a connection to a running app, so are a bit more limited in general.

Their entry module anatomy is as follows:

import {DevicePluginClient} from 'flipper-plugin';

export function devicePlugin(client: DevicePluginClient) {
return {}; // API exposed from this plugin
}

export function Component() {
// Plugin UI
return <h1>Welcome to my first plugin</h1>;
}

Client plugins must have the property pluginType set to device and should specify supported devices using the property supportedDevices in package.json.

The supportedDevices property should contain an array of supported devices, each defined as a conjunction of device properties, using the following format:

{ "os": <"Android" | "iOS" | "Metro">, "type": <"physical" | "emulator">, "archived": <true | false> }

For example, the array { "os": "Android", "type": "emulator" } indicates that device must work on Android AND must be an emulator in order to debug it using the plugin.

To specify that a plugin supports all types of Android devices, and physical iOS devices, and does not support imported (archived) data, the plugin package.json should look like the following:

{
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "flipper-plugin-mydeviceplugin",
"id": "mydeviceplugin",
"pluginType": "device",
"supportedDevices": [
{"os": "Android", "archived": false},
{"os": "iOS", "type": "physical", "archived": false}
]
...
}
rmation

Generally, device plugins function in a similar manner to normal client plugins.

The available APIs for device plugins are listed in the Desktop Plugin API page.

Creating a simple table plugin

Flipper provides a standard abstraction to render data received from a Client plugin in a table, see createTablePlugin in the 'Desktop Plugin API' page.

Validation

Plugin definition can be validated using command flipper-pkg lint. The command shows all the mismatches that need to be fixed to make the plugin definition valid.

Transpilation and bundling

Flipper has tooling for transpiling and bundling that enables the creation of plugins in plain ES6 JavaScript or TypeScript.

The following are recommended:

  • Use TypeScript for the best development experience.
  • Use .tsx when using TypeScript, which adds support for inline React expressions.

You may recall that the Flipper development build automatically transpiles and bundles plugins on loading. It's capable of all ES6 functionality, Flow annotations, TypeScript, as well as JSX, and applies the required babel-transforms.

In contrast, the Flipper release build does not transpile or bundle plugins on loading. For production usage, plugins should be bundled before publishing using flipper-pkg. This utility applies the same modifications as the plugin loader of the development build.

The flipper-pkg tool is published to npm and should be installed as a devDependency for the plugin package.

Then, to bundle the plugin, execute the following command in its folder:

yarn flipper-pkg bundle

This command reads the package.json, takes the path specified in the flipperBundleEntry field as entry point, transpiles and bundles all the required code, and outputs the produced bundle to the path specified in field main.

You can get the list of other available commands by invoking flipper-pkg help, and get detailed description for any command by invoking flipper-pkg help [COMMAND]. For usage details, see the flipper-pkg page on the npmjs.com web site.

npm dependencies

If you need any dependencies in your plugin, you can install them using yarn add.

caution

Flipper plugins must be designed to work inside browsers. For that reason, you should avoid using Node.JS APIs directly (such as modules like fs, child_process, path), or packages that depend on them. For alternative APIs, see using Node.js APIs in Flipper plugins.

- + \ No newline at end of file diff --git a/docs/extending/dev-setup/index.html b/docs/extending/dev-setup/index.html index b39fb3c5aa4..a43b4c5caca 100644 --- a/docs/extending/dev-setup/index.html +++ b/docs/extending/dev-setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Development Setup

IDE

When developing Flipper plugins, the following IDEs are recommended:

  • TypeScript (for Flipper Desktop (plugins)): Visual Studio Code
    • Install the "ESLint" (dbaeumer.vscode-eslint) extension from marketplace to enable linting.
    • Install the "Prettier" (esbenp.prettier-vscode) extension to enable automatic code-formatting.
    • If for some reason it is not working, the builtin TypeScript extension might be disabled. To enable it, to go to extensions, search for “@builtin typescript” and enable it.
  • Android Studio (for Android plugins).
  • XCode (for iOS plugins).

When developing Flipper plugins, it's strongly recommended to run from Flipper itself from source as well, as this yields the following benefits:

  • Automatic transpilation and bundling of loaded plugins: ES6, TypeScript, JSX.
  • Automatic refresh after code changes.
  • React and Redux Dev Tools.
  • Debugging using Chrome Dev Tools or Visual Studio Code.
  • Additional debug information like React warnings and performance metrics.

Prerequisites for a Flipper development build:

  • node ≥ 14
  • yarn ≥ 1.5
  • git
  • watchman

To run Flipper Desktop from source:

git clone https://github.com/facebook/flipper.git
cd flipper/desktop
yarn
yarn start
Tip

Start with yarn start --fast-refresh for experimental fast refreash.

Startup options

You can use several options to customise development build instance. They can be provided as command-line args or environment variables.

You can check all of them by executing yarn start --help:

yarn start [args]

Options:
--embedded-plugins Enables embedding of plugins into Flipper bundle. If it
disabled then only installed plugins are loaded. The
flag is enabled by default. Env var
FLIPPER_NO_EMBEDDED_PLUGINS is equivalent to the
command-line option "--no-embedded-plugins". [Boolean]
--fast-refresh Enable Fast Refresh - quick reload of UI component
changes without restarting Flipper. The flag is disabled
by default. Env var FLIPPER_FAST_REFRESH is equivalent
to the command-line option "--fast-refresh". [Boolean]
--plugin-auto-update [FB-internal only] Enable plugin auto-updates. The flag
is disabled by default in dev mode. Env var
FLIPPER_NO_PLUGIN_AUTO_UPDATE is equivalent to the
command-line option "--no-plugin-auto-update" [Boolean]
--enabled-plugins Load only specified plugins and skip loading rest. This
is useful when you are developing only one or few
plugins. Plugins to load can be specified as a
comma-separated list with either plugin id or name used
as identifier, e.g. "--enabled-plugins
network,inspector". The flag is not provided by default
which means that all plugins loaded. [array]
--open-dev-tools Open Dev Tools window on startup. The flag is disabled
by default. Env var FLIPPER_OPEN_DEV_TOOLS is equivalent
to the command-line option "--open-dev-tools". If
"FLIPPER_UPDATE_DEV_TOOLS=true" is set additionally,
Flipper will try to update the dev tools from the play
store. [Boolean]
--dev-server-port Dev server port. 3000 by default. Env var "PORT=3001" is
equivalent to the command-line option "--dev-server-port
3001". [number] [default: 3000]
--version Show version number [Boolean]
--help Show help [Boolean]

You can also create an .env file in the desktop subfolder and specify any environment variables to load them automatically on yarn start so you don't need to pass command-line args every time:

FLIPPER_FAST_REFRESH=true
FLIPPER_OPEN_DEV_TOOLS=true
FLIPPER_ENABLED_PLUGINS=flipper-messages,network,inspector

Guidelines for writing TypeScript

  • Install 3rd party type definitions as dev dependency (for example, yarn add @types/lodash --dev)

Submitting a diff / PR

Make sure your new functionality is covered with tests and run yarn test or yarn test --watch in the desktop/ directory to verify that they pass.

See the testing page for more details on writing and running tests.

To ensure you don't get any lint/formatting errors, run yarn lint before submitting your diff. Some errors (especially formatting errors) can be auto-fixed by running yarn fix

- + \ No newline at end of file diff --git a/docs/extending/error-handling/index.html b/docs/extending/error-handling/index.html index 84e0afc9f07..2ba714a8121 100644 --- a/docs/extending/error-handling/index.html +++ b/docs/extending/error-handling/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Error Handling

Errors in Flipper plugins should be hidden from the user while providing actionable data to the plugin Developer.

Android

To gracefully handle errors in Flipper, use the following classes:

  • FlipperResponder - an instance is provided to the client plugin on every method call, which enables it to return results. When an error occurs during a Flipper method call that can't be handled, pass the error to the responder. This will let the desktop plugin handle it, and if it doesn't, the error will be displayed in the DevTools console.

  • ErrorReportingRunnable - a custom Runnable that catches all exceptions, stopping them from crashing the application and reports them to Flipper. These error messages will show up in the DevTools console.

Executing the following block of code will always finish without error but may transfer any silent errors to the Flipper desktop app:

new ErrorReportingRunnable(mConnection) {
@Override
public void runOrThrow() throws Exception {
mightThrowException();
}
}.run();

During plugin development these java stack traces are surfaced in the chrome dev console.

Always use ErrorReportingRunnable for error handling instead of try/catch or, even worse, letting errors crash the app. With ErrorReportingRunnable you won't block anyone, and you won't hide any stack traces.

C++

To gracefully handle errors in Flipper, all transactions are performed inside of a 'Try' block, which catches all exceptions, stops them from crashing the application, and reports them to the plugin Developer. This includes your own customs implementations of FlipperPlugin::didConnect() and FlipperConnection::send() and ::receive().

This means you can safely throw exceptions in your plugin code; the exception messages will be sent to the Flipper desktop app.

During plugin development the exception messages are surfaced in the Chrome dev console.

If your plugin performs asynchronous work in which exceptions are thrown, these exceptions will not be caught by the Flipper infrastructure. You should handle them appropriately.

- + \ No newline at end of file diff --git a/docs/extending/establishing-a-connection/index.html b/docs/extending/establishing-a-connection/index.html index f71a0147523..0a45e2f7e21 100644 --- a/docs/extending/establishing-a-connection/index.html +++ b/docs/extending/establishing-a-connection/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Secure Communication

This page provides an outline of how a connection is established between an app, with the Flipper SDK integrated, and the desktop app. This connection occurs behind the scenes inside the mobile SDK, so users shouldn't need to worry about it.

To prevent Flipper clients on mobile apps from connecting to any server that happens to be running on localhost and potentially leaking information from your app, the connection process is a little more involved than you may expect.

Transport Protocol

Flipper uses WebSocket to communicate between the desktop and mobile apps. WebSocket enables bi-directional communication.

Client-Server relationship

When the desktop app starts up, it opens a secure socket on port 9088. The Flipper client will continually attempt to connect to this port on localhost to establish a connection with the desktop app.

Certificate Exchange

To avoid mobile apps from connecting to untrusted ports on localhost, a Flipper client should only connect to servers that have a valid, trusted TLS certificate. In order for the mobile app to know which certificates it can trust, it conducts a certificate exchange with the desktop app before it can make its first secure connection.

This is achieved through the following steps:

  1. Desktop app starts an insecure server on port 9089.
  2. Mobile app connects to localhost:9089 and sends a Certificate Signing Request to the desktop app.
  3. Desktop app uses its private key (this is generated once and stored in ~/.flipper) to sign a client certificate for the mobile app.
  4. Along with the Certificate Signing Request, mobile app also lets the desktop app know which Certificate Exchange Medium to use:
    • Certificate Exchange Medium = FS_ACCESS - the desktop uses ADB (for Android) or the mounted file system (for iOS simulators) to write the following files to the mobile app's private data partition:
      • Server certificate that the mobile app can now trust.
      • Client certificate for the mobile app to use going forward.
    • Certificate Exchange Medium = WWW - the desktop app will use your implementation of Certificate Uploader to upload the certificates:
      • Once uploaded, the desktop app will reply back with the device id, which can be used by the Certificate Provider on the client side to fetch those certificates.
Currently, Flipper does not support WWW mode but there is work underway to start support.

Now that the mobile app knows which server certificate it can trust, you can connect to the secure server.

note

This enables the mobile app to trust a certificate if and only if it is stored inside its internal data partition. Typically, it's only possible to write there with physical access to the device (that is, through ADB or a mounted simulator).

To get the desktop app to generate a client certificate for your client and then deploy it, go through the following steps:

  1. Use a WebSocket client to connect (insecurely) to the following URL (Parameters are defined in Implementing a Flipper Client):

    localhost:9089/sonar?os={OS}
    &device={DEVICE}
    &app={APP}
    &sdk_version={SDK_VERSION}
    &medium={CERTIFICATE_EXCHANGE_MEDIUM}
  2. On that connection, send the following payload:

    Request = {
    "method": "signCertificate",
    "csr": string,
    "destination": string,
    "medium": int
    }
    • csr - a Certificate Signing Request the client has generated, and destination identifies a location accessible to both the client and Flipper desktop, where the certificate should be placed.
      • The subject Common Name (CN=...) must be included in the CSR. Your CertificateProvider implementation in Flipper may use this in combination with the destination to determine where to put the certificate. This will ask Flipper desktop to generate a client certificate, using the CSR provided, and put it into the specified destination.
    • destination - depending on the client, destination can have a different meaning:
      • Basic example - a file path that both the desktop and the client have access to. With this, Flipper desktop could write the certificate to that path.
      • Advanced example - an Android Client for which the destination specifies a relative path inside an app container: the Subject Common Name determines which app container. Together, these two pieces of information form an absolute file path inside an android device.
      • For the Flipper desktop to work with a given Client type, it needs to be modified to know how to correctly interpret the destination argument and deploy certificates to it.
    • medium - the destination field may not be relevant if your medium value is more than 1:
      • medium=1(default) - Flipper should do certificate exchange by directly putting certificates at destination in the sandbox of the app.
      • medium=2 - Flipper will use the Certificate Uploader and Provider to upload certificates and download it on the client side respectively.

You can see the current implementations in CertificateProvider.tsx.

- + \ No newline at end of file diff --git a/docs/extending/flipper-plugin/index.html b/docs/extending/flipper-plugin/index.html index e570a416e2e..b40ac951c64 100644 --- a/docs/extending/flipper-plugin/index.html +++ b/docs/extending/flipper-plugin/index.html @@ -17,7 +17,7 @@ - + @@ -103,7 +103,7 @@ Again, see the tutorial how to interact with this object in general. The test runner is a bag full of utilities, but typically it is fine to just destructure the utilities relevant for the test. Exposed members:

  • instance: The object (public API) returned from your plugin definition. You will typically use this in most tests, either to trigger updates or to inspect the current state of the plugin.
  • exportState(): Grabs the current state of all persist enabled state atoms. The object format returned here is the same as in the initialState option.
  • activate(): Emulate the onActivate event. By default, startPlugin already starts the plugin in activated state and calling activate to test the onActivate event should be preceded by a deactivate() call first.
  • deactivate(): Emulates a user navigating away from the plugin.
  • destroy(): Emulates the plugin being cleaned up, for example because the plugin is disabled by the user, or because the device / client has disconnected. After calling destroy the current runner is unusable.
  • triggerDeepLink(payload): Emulates a deepLink being triggered and fires the onDeepLink event.
  • triggerMenuEntry(label): Emulates the user clicking a menu entry in the Flipper main menu.
  • flipperLib: An object that exposed jest.fn() mocks for all built-in Flipper APIs that can be called by your plugin. So, assertions can be made that the plugin did actually invoke those methods. For example: expect(runner.flipperLib.createPaste).toBeCalledWith("test message"). Currently supported mocks: createPaste, enableMenuEntries.

The following members are available when using the render... variant rather than the start... variant:

  • renderer: This object can be used to query the DOM and further interact with it. It is provided by react-testing-library, and further documented in the render Result page.
  • act: Use this function to wrap interactions with the plugin under test into a transaction, after which the DOM updates will be flushed by React. See also the React.js act() documentation.

The following members are only available for Client plugins:

  • sendEvent(event, params): Emulates an event being sent by the client plugin. Will trigger the corresponding onMessage handler in the plugin.
  • sendEvents({ method: string, params: object}[]): Like sendEvent, but sends multiple events at once.
  • onSend: A jest.fn() that can be used to assert that client.send() was called by the plugin under test. For example, expect(runner.onSend).toBeCalledWith('currentLogs', { since: 0}).
  • connect(): Triggers the onConnect() event. (For non-background plugins activate() could as well be used for this).
  • disconnect(): Triggers the onDisconnect() event. (For non-background plugins deactivate() could as well be used for this).

The following members are only available for Device plugins:

  • sendLogEntry(logEntry): Emulates a log message arriving from the device. Triggers the client.device.onLogEntry listener.
- + \ No newline at end of file diff --git a/docs/extending/layout-inspector/index.html b/docs/extending/layout-inspector/index.html index f7b8d994d47..9e94b9d9751 100644 --- a/docs/extending/layout-inspector/index.html +++ b/docs/extending/layout-inspector/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Extending the Layout Inspector

The Layout Inspector plugin can be extended to support new kinds of UI components. You can also extend it to customize the data made available in the sidebar. Depending on whether or not you want to expose new data on Android or iOS, there are different interfaces you can use.

The following screenshot shows the Layout Inspector in action.

Layout Inspector

Android

NodeDescriptor

To expose an object to the Layout Inspector in Flipper, you have to implement a NodeDescriptor that describes your object. For example, the ViewDescriptor describes View objects and the FragmentDescriptor describe Fragment instances. These descriptors have a set of callbacks used to expose children and data associated with the object they describe.

For the full API, see See NodeDescriptor.java in GitHub.

NodeDescriptor implementations should not subclass other NodeDescriptor implementations. Instead, re-use existing behavior from a more generic descriptor, it's best to use a delegate.

Following are code snippets that illustrate how to use and how not to use the NodeDescriptor on Android.

How to use the NodeDescriptor on Android

class ViewGroupDescriptor extends NodeDescriptor<ViewGroup> {
public String getName(ViewGroup node) {
NodeDescriptor descriptor = descriptorForClass(View.class);
return descriptor.getName(node);
}
}

How not to use the NodeDescriptor on Android

class ViewGroupDescriptor extends ViewDescriptor<ViewGroup> {
public String getName(ViewGroup node) {
return super.getName(node);
}
}

Register a descriptor

Register your descriptor in the DescriptorMapping used to instantiate the InspectorFlipperPlugin:

final FlipperClient client = FlipperClient.createInstance(mContext);
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
descriptorMapping.register(MyObject.class, new MyObjectDescriptor());
client.addPlugin(new InspectorFlipperPlugin(mContext, descriptorMapping));

Extending an existing descriptor

You may not need to create a whole new descriptor. Instead, you may just want to change extend an existing one to expose some new piece of data. In such a case, just locate the correct descriptor and edit its getData, getAttributes, and perhaps setData methods.

iOS

SKNodeDescriptor

To expose an object to the layout inspector in Sonar, you have to implement a SKNodeDescriptor that describes the object. For example, SKViewDescriptor describes UIView objects, and the SKComponentDescriptor describes CKComponent objects. These descriptors have necessary callbacks that are used to expose its children and data associated with the object they describe.

For the full available API, see SKNodeDescriptor.h in GitHub.

SKNodeDescriptor implementations should never be subclass other SKNodeDescriptor implementations. Instead, re-use existing behaviour by explicitly using other descriptors and delegate behaviour.

Following are code snippets that illustrate how to use and how not to use the SKNodeDescriptor on iOS.

How to use the SKNodeDescriptor on iOS

@interface SKArbitraryViewDescriptor : SKNodeDescriptor<ArbitraryView *>
@end

@implementation SKArbitraryViewDescriptor

- (NSString *)identifierForNode:(ArbitraryView *)node
{
SKNodeDescriptor *descriptor = [self descriptorForClass:[UIView class]];
return [descriptor identifierForNode:node];
}

@end

How not to use the SKNodeDescriptor on iOS

@interface SKArbitraryViewDescriptor : SKViewDescriptor<ArbitraryView *>

@end

@implementation SKArbitraryViewDescriptor

- (NSString *)identifierForNode:(ArbitraryView *)node
{
return [super identifierForNode:node];
}

@end

Register a Descriptor

In order to register your descriptor for an object, use SKDescriptorMapper. After registering all descriptors, pass on the descriptor-mapper object to the plugin during initialisation:

[descriptorMapper registerDescriptor:[SKArbitraryViewDescriptor new]
forClass:[ArbitraryView class]];

There's already a set of descriptors registered by default in SKDescriptorMapper. If you want to add a descriptor to the default set, you can do it in the SKDescriptorMapper.

Extending an existing Descriptor

Sometimes, all you need is to extend the functionality of an existing descriptor. In such as case, you just need to locate the correct descriptor and edit the methods dataForNode, attributesForNode, and possibly dataMutationsForNode.

Subdescriptors

If you want to extend the SKComponentKitLayoutDescriptor and add an additional section based on the nodes of the SKComponentLayoutDescriptor, you can use SKSubDescriptor:

#import <FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h>

NSString *YourSubDescriptor(SKComponentLayoutWrapper *)node {
return @"Meta data";
}

// At setup time, you must register it:
[SKComponentLayoutDescriptor registerSubDescriptor:&YourSubDescriptor forName:@"Section Name"];

Swift support is not yet available because you must access SKComponentLayoutWrapper to implement a subdescriptor.

- + \ No newline at end of file diff --git a/docs/extending/loading-custom-plugins/index.html b/docs/extending/loading-custom-plugins/index.html index 9c9f2efd1cb..96755330ec5 100644 --- a/docs/extending/loading-custom-plugins/index.html +++ b/docs/extending/loading-custom-plugins/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Dynamically Loading Plugins

Flipper loads and runs plugins it finds in a configurable location. The paths searched are specified in ~/.flipper/config.json. These pluginPaths should contain one folder for each of the plugins it stores.

An example config setting and plugin file structure is shown below:

~/.flipper/config.json:

{
...,
"pluginPaths": ["~/flipper-plugins"]
}

Plugin File example structure:

~ flipper-plugins/
my-plugin/
package.json
src/index.tsx
dist/bundle.js
note

When using npx flipper-pkg init for scaffolding, as explained in the tutorial or on the next page, the path should be configured automatically for you in most cases.

Typically, the above setup is only needed if you are developing plugins.

To consume plugins, it is recommended to use one of the existing distribution mechanisms

- + \ No newline at end of file diff --git a/docs/extending/new-clients/index.html b/docs/extending/new-clients/index.html index 84029cbe9ec..e96c92cd555 100644 --- a/docs/extending/new-clients/index.html +++ b/docs/extending/new-clients/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Implementing a Flipper Client

In the GitHub repo, you'll find Flipper clients for Android, iOS and C++ code, but there's nothing to stop you from writing a FlipperClient for another device or OS.

Flipper clients communicate with the Flipper desktop app using JSON RPC messages over a WebSocket connection.

This page documents the API. For reference, use the FlipperConnectionManagerImpl.cpp.

Establishing a connection

Start by connecting to the Flipper server running within the desktop app. Connecting to the server registers your application with Flipper and enables plugins to interact with it. As the Flipper desktop has a different lifecycle than your app and may connect and disconnect at any time, it's important that you continue to attempt to reconnect to the Flipper server until it accepts your connection.

The WebSocket protocol is used for communication between desktop and client because it enables easy certificate pinning and functionality, such as with request-response messages.

In order to securely connect to Flipper, your client should first obtain a certificate.

After the client certificate has been obtained, connect to the following URL with a WebSocket client:

localhost:9088/sonar?os={OS}
&device={DEVICE}
&device_id={DEVICE_ID}
&app={APP}
&sdk_version={SDK_VERSION}
&foreground={FOREGROUND}

The URL parameters are detailed in the following table.

ParameterDetailExample
OSThe OS from which the connection is being established. This is usually hard coded into the FlipperClient implementation. This string may be used by the Flipper desktop app to identify valid plugins as well as present in the UI to the user.os=Android (If your client is running on Android).
DEVICEThe name of the device running the application.device=iPhone7
DEVICE_IDA unique identifier for the device. The Flipper server / desktop app may use this to coalesce multiple connections originating from the save device or present the string in the UI to differentiate between connections to different clients.
APPThe name of the app running this client instance. The combination of OS + DEVICE_ID + APP uniquely identifies a connection.app=Facebook (When connecting to a running facebook app).
FOREGROUND (Optional)A Boolean indicating whether this connection was established with a foreground process. This is a hint to the Flipper desktop app of whether to re-focus on this connection or not. Though optional, this paramater is recommended.foreground=true
SDK_VERSIONA number indicating the latest protocol version with which the client is compatible. You can find the current version in the C++ connection implementation. Usually stored as a constant in the client code, this allows protocol changes to be made whilst still preserving connectivity with old clients. When Flipper desktop encounters an old SDK version, it may attempt to communicate using a matching protocol. However, backwards compatibility is not guaranteed, and you should strive to update clients on the rare occasion that the protocol version advances.

Responding to messages

Flipper uses a simple Remote Procedure Call (RPC) protocol using JSON-formatted payloads:

  • The method field of the payload indicates which method of the FlipperClient is being called. This will always be present.
  • The payload field contains the JSON parameters for the method call. This may be omitted when no parameters are used.

It is recommended that implementations gracefully ignore extra fields for the sake of backwards and forwards compatibility.

Responses contain either a success object representing the return value of the RPC invocation or an error object indicating that an error occurred.

Methods

The methods detailed in the following sub-sections must be implemented by all FlipperClient implementations.

The syntax used for these type definitions is Flow. All requests/responses are JSON objects. Where no Response type is specified, it's a void call - no response is expected.

getPlugins

Return the available plugins as a list of identifiers. A plugin identifier is a string which is matched with the plugin identifier of desktop javascript plugins. This allows the client to specify the plugins it supports.

Request = {
"method": "getPlugins",
}

Response = {
"success": {
"plugins": Array<string>
},
}

getBackgroundPlugins

Returns a subset of the available plugins returned by getPlugin. The background connections will automatically receive a connection from Flipper once it starts (and if the plugins are enabled), rather than waiting for the user to open the plugin.

Request = {
"method": "getBackgroundPlugins",
}

Response = {
"success": {
"plugins": Array<string>
},
}

init

Initialize a plugin. This should result in an onConnected call on the appropriate plugin. Plugins should by nature be lazy and should not be initialized up front as this may incur significant cost. The Flipper desktop client knows when a plugin is needed and should control when to initialize them.

Request = {
"method": "init",
"params": {
"plugin": string,
},
}

deinit

This is the opposite of init. A call to deinit is made when a plugin is no longer needed and should release any resources. Don't rely only on deinit to release plugin resources as Flipper may quit without having the chance to issue a deinit call. In such ases, you should also rely on the WebSocket disconnect callbacks. This call is mainly for allowing the desktop app to control the lifecycle of plugins.

Request = {
"method": "deinit",
"params": {
"plugin": string,
},
}

execute

This is the main call and how the Flipper desktop plugins and client plugins communicate. When a javascript desktop plugin issues a client request, it will be wrapped in one of these execute calls. This execute indicates that the call should be redirected to a plugin:

  • request.params.api - the plugin id.
  • request.params.method - the method within the plugin to execute.
  • request.params.params - an optional params object containing the parameters to the RPC invocation.
Request = {
"method": "execute",
"params": {
"api": string,
"method": string,
"params": ?Object,
},
}

Response = {
"success": Object,
} | {
"error": Object,
}

Error Reporting

The Flipper desktop app handles error reporting. If an error occurs during the execution of an RPC invocation, it returns a serialization of it in the response so it can be attributed to the method call.

If an error occurs in some other context, you can proactively send it to Flipper with the following request structure:

Request = {
error: {
message: string,
stacktrace: string,
}
}

While in development mode, Flipper will display any client errors next to javascript errors in the Chrome Developer Tools console.

Testing

Testing is incredibly important when building core infrastructure and tools. The following is pseudocode for tests any new FlipperClient implementation is expected to implement and correctly execute. To run tests, it's strongly recommended that you to build a mock for the WebSocket connection to mock out the desktop side of the protocol and to not have any network dependencies in your test code.

test("GetPlugins", {
let connection = new MockConnection();
let client = new FlipperClient(connection);
let plugin = {id: "test"};

client.addPlugin(plugin);
client.start();

connection.onReceive({
id: 1,
method: "getPlugins",
});

assert(connection.sentMessages, contains({
id: 1,
success:{
plugins: ["test"],
},
}));
});
test("InitDeinit", {
let connection = new MockConnection();
let client = new FlipperClient(connection);
let plugin = {id: "test", connected: false};

client.addPlugin(plugin);
client.start();

assertFalse(plugin.connected);

connection.onReceive({
id: 1,
method: "init",
params: {
plugin: "test",
},
});

assertTrue(plugin.connected);

connection.onReceive({
id: 1,
method: "deinit",
params: {
plugin: "test",
},
});

assertFalse(plugin.connected);
});
test("Disconnect", {
let connection = new MockConnection();
let client = new FlipperClient(connection);
let plugin = {id: "test", connected: false};

client.addPlugin(plugin);
client.start();

assertFalse(plugin.connected);

connection.onReceive({
id: 1,
method: "init",
params: {
plugin: "test",
},
});

assertTrue(plugin.connected);
connection.disconnect();
assertFalse(plugin.connected);
});
test("Execute", {
let connection = new MockConnection();
let client = new FlipperClient(connection);
let plugin = {
id: "test",
reverse: (params, responder) => {
responder.success({word: params.word.reverse()});
},
};

client.addPlugin(plugin);
client.start();

connection.onReceive({
id: 1,
method: "init",
params: {
plugin: "test",
},
});

connection.onReceive({
id: 1,
method: "execute",
params: {
api: "test",
method: "reverse",
params: {
word: "hello"
},
},
});

assert(connection.sentMessages, contains({
id: 1,
success:{
word: "olleh",
},
}));
});
- + \ No newline at end of file diff --git a/docs/extending/node-apis/index.html b/docs/extending/node-apis/index.html index 70a31d34ce4..2c137b7c26a 100644 --- a/docs/extending/node-apis/index.html +++ b/docs/extending/node-apis/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Using Node.js APIs

Flipper plugins must be designed to work inside browsers. For that reason, you should avoid using Node.JS APIs directly (with, for example, modules like fs, child_process, path), or packages that depend on the plugins.

The most important Node APIs can be found by using getFlipperLib() (exposed by the flipper-plugin package). Please note that these APIs are all promisified:

  • fs - use getFlipperLib().remoteServerContext.fs instead.
  • child_process - use getFlipperLib().remoteServerContext.childProcess.exec. Note that this API is intended for short lived processes only.
  • path - use import {path} from 'flipper-plugin' instead.
  • os - use getFlipperLib().environmentInfo.os instead.
    • For system-specific directories such as 'home' and 'desktop', use getFlipperLib().paths.homePath and similar.

In the future, these APIs may be subject to further security / permission restrictions to better sandbox plugins.

- + \ No newline at end of file diff --git a/docs/extending/plugin-distribution/index.html b/docs/extending/plugin-distribution/index.html index 743d69009bd..fcc3743881c 100644 --- a/docs/extending/plugin-distribution/index.html +++ b/docs/extending/plugin-distribution/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Plugin Distribution

Publishing to npm

Flipper plugins are essentially standard npm packages, which means you can publish them by executing yarn publish or npm publish in the plugin directory.

The only requirements are:

  1. package.json and code must follow the Flipper plugin specification

  2. Code must be bundled using "flipper-pkg" before packing or publishing. This can be done by executing flipper-pkg bundle on prepack step:

    {
    ...
    "devDependencies": {
    ...
    "flipper-pkg": "latest"
    },
    "scripts": {
    ...
    "prepack": "flipper-pkg bundle"
    }
    }

Packaging to File

To package a plugin as a tarball, you can use the same command as for packaging any other npm package (using yarn pack or npm pack).

flipper-pkg also provides a convenient command pack that does the following:

  1. Installs the plugin dependencies.
  2. Bundles the plugin.
  3. Creates the tarball and saves it at the specified location.

Example

To package a plugin located at ~/flipper-plugins/my-plugin to ~/Desktop, execute the following command:

flipper-pkg pack ~/flipper-plugins/my-plugin -o ~/Desktop

Installation from File

It's possible to install plugins into Flipper from tarballs. This is useful in cases when you need to try a plugin version that is not published to npm, or if you want to distribute plugin privately. Take the following steps:

  1. Launch Flipper.
  2. Click the 'Manage Plugins' button in the bottom-left corner.
  3. Select the 'Install Plugins' tab in the opened sheet.
  4. Specify the path to the plugin package (or just drag and drop it) and click 'Install'.
- + \ No newline at end of file diff --git a/docs/extending/power-search/index.html b/docs/extending/power-search/index.html index 6541805eba8..f6e337a4d8c 100644 --- a/docs/extending/power-search/index.html +++ b/docs/extending/power-search/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Power Search

By default, your table has a power search bar. It allows to search through the entire row as a serialized string or through individual columns. Based on the column type, power search provides different search operators for columns. For instance, for string values it can check if a string contains a substring or even matches some other string exactly. At the same time, for dates Flipper can filter out records after or before a certain date. Since Flipper does not have a way of identifying the column type in advance, it always assumes that every column is a string. If you want you can tell Flipper how to handle a column and what power search operators should be available.

Simplified config

Power search provides a list of default predicates for every column data type. You can specify the column data type like this:

import {DataTableColumn} from 'flipper-plugin'

type MyRow = {
timestamp: number;
eventType: string;
}

const columns: DataTableColumn<MyRow>[] = [
{
key: 'timestamp',
title: 'Timestamp',
sortable: true,
powerSearchConfig: {type: 'dateTime'},
},
{
key: 'eventType',
title: 'Event',
powerSearchConfig: {type: 'enum'}
},
]

Complete list of possible "types".

Advanced config

If the default list of predicates is not tailored enouhg for your use-case, you can provide a list of predicates explicitly.

import {DataTableColumn, dataTablePowerSearchOperators} from 'flipper-plugin'

type MyRow = {
timestamp: number;
eventType: string;
}

const EVENT_TYPE_ENUM_LABELS = {
yodaValue: 'Yoda Label',
lukeValue: 'Luke Label'
}

const columns: DataTableColumn<MyRow>[] = [
{
key: 'timestamp',
title: 'Timestamp',
sortable: true,
powerSearchConfig: [
dataTablePowerSearchOperators.same_as_absolute_date_no_time(),
]
},
{
key: 'eventType',
title: 'Event',
powerSearchConfig: {
// You can also provide power search config as an object
operators: [
dataTablePowerSearchOperators.enum_is(EVENT_TYPE_ENUM_LABELS),
dataTablePowerSearchOperators.enum_is_not(EVENT_TYPE_ENUM_LABELS),
],
// It could have exra options
// See https://github.com/facebook/flipper/blob/main/desktop/flipper-plugin/src/ui/data-table/DataTableWithPowerSearch.tsx#L157
}
},
]

While we would encourage using the new power search, some plugins might decide to stick to the legacy experience. To do that you have to use different imports from 'flipper-plugin': MasterDetailLegacy instead of MasterDetail, DataTableLegacy instead of DataTable, DataTableColumnLegacy instead of DataTable, DataTableManagerLegacy instead of DataTableManager.

import {MasterDetailLegacy, DataTableColumnLegacy} from 'flipper-plugin';

const columns: DataTableColumnLegacy<MyRow>[] = [
// colun definition
]

export const Component = () => {
return <MasterDetailLegacy columns={columns} /* ...other props */ />
}

Examples

You can see a live examplse of how you can provide the power search config here:

  1. Logs
  2. Network
  3. Intern-only.

You can find the complete list of supported operators here.

- + \ No newline at end of file diff --git a/docs/extending/public-releases/index.html b/docs/extending/public-releases/index.html index 7e10d763ad3..e8b5c5a8d50 100644 --- a/docs/extending/public-releases/index.html +++ b/docs/extending/public-releases/index.html @@ -17,7 +17,7 @@ - + @@ -31,7 +31,7 @@ The workflow is defined in publish-npm.yml.

From there, we use a script to bump the versions of our Yarn workspaces, and publish all public packages (flipper, flipper-babel-transformer, ...) and our React Native bindings.

note

The authentication to npm is managed by a secret environment variable called FLIPPER_NPM_TOKEN.

Android Release

Android has three types of jobs currently running:

  1. snapshot - runs on every commit on the main branch and publishes 'SNAPSHOT' releases to Maven Central. It runs on CircleCI.
  2. publish-android - usually triggered by a dispatch_workflow event. It uploads our Java artifacts to Maven Central and attaches the Android sample app to the release page on GitHub. It runs on GitHub Actions.
  3. android-sample - runs on every push and open pull request. It builds the sample and tutorial apps and uploads the sample APK as artifact for easy debugging and testing.

The snapshot job is an outlier in that it still runs on CircleCI. This gives us some additional capacity as these jobs can take quite a while and the occasional failure due to timeouts or network errors isn't a dealbreaker.

CircleCI Configuration

The Android snapshot build is run on CircleCI and configured in .circleci/config.yml.

There are two potential points for breakage:

  1. Base image - used in the build instructions refers to a specific SDK version and requires occasional updating.
  2. Platform installation - runs through the sdkmanager tool of the Android SDK. It may require additional SDKs or NDKs to be installed if they're not part of the base image.

One non-obvious aspect is that of authentication for uploads. The repository contains a symmetrically encrypted copy of our credentials to Sonatype (for Maven Central). The snapshot release script decodes the file on the fly by using a secret Circle CI exposes through an environment variable.

GitHub Action Workflow

As with the iOS release (see above), the workflow for Android releases is triggered by three types of events:

  1. When a tag is pushed.
  2. By manually triggering the workflow.
  3. Through a dispatch_workflow event, which is issued as a last step of the desktop release process.

In normal circumstances, the third event will kick off an Android release build. The workflow is defined in publish-android.yml in GitHub.

We first install two NDK versions that are required by our dependencies. To publish release artifacts (non-SNAPSHOT artifacts), that Maven Central, requires them to be signed with a GnuPG key. The only requirement about the key is that it needs to be exported to a Keyserver. Ours is published to the Ubuntu Keyserver.

To publish your own key, run the following:

gpg --send-keys --keyserver keyserver.ubuntu.com <KEY_ID>

For the initial setup, the secret keyring was exported as gpg2 --export-secret-keys <secret_key_id> | base64 and stored as a secret on GitHub with the name GPG_KEY_CONTENTS.

As part of the workflow, it is written to disk after reversing the base64 encoding. The key id and key password are subsequently stored in the gradle.properties along with the path to the key. Paths here need to be absolute, otherwise Gradle will look them up relative to the sub-projects (android/, android/sample, ...).

Maven Central is managed by Sonatype. To sign up follow their Getting Started guide, which involves creating a JIRA account and opening an issue to apply for the com.facebook namespace. You will need to find an existing member of this namespace to vouch for you. While this is a bit of a task, it ensures that nobody from outside the organisation can publish under our name.

The publish (previously uploadArchives) gradle task uses the OSSRH Sonatype Nexus credentials to upload all Flipper Java artifacts. This includes the core SDK as well as our plugins. The credentials are not your login to Nexus, but the user tokens you can get from your profile.

This is followed by the closeAndReleaseRepository gradle task, which is part of the gradle-maven-publish-plugin. It uses the credentials to identify a 'staging repository' and automatically close it. This staging repository is identified by the SONATYPE_STAGING_PROFILE property. Sonatype usually requires people to manually go to a web UI, verify that a given release is complete and click some buttons. The plugin aims to do this for you.

Troubleshooting

There are a few potential 'troubles':

  • Upload fails - Maven Central is (at the time of writing) overloaded with projects migrating from JCenter. The upload task attempts to retry but it can still time out. Manually re-running the job through the GitHub UI should do the trick.

  • Closing fails - as before, this can happen because of timeouts.

  • Retrying to close fails because of duplicate staging repositories - particularly annoying because you can't fix this through automation. It happens when artifacts are uploaded multiple times and now more than one staging repository exists. You must first drop (not close or release) the existing ones before restarting the job.

    Take the following steps:

  • NDK mismatch - if Gradle complains about a missing NDK, this usually indicates that a dependency has a hard requirement on a particular NDK. You can add it to the list in the sdkmanager command.

  • Artifacts not available - Maven Central syncs with a delay of sometimes a few hours. You can check directly on the Maven2 main server if the artifacts with the new version number are uploaded.

- + \ No newline at end of file diff --git a/docs/extending/sandy-migration/index.html b/docs/extending/sandy-migration/index.html index addfacd5452..886447f564c 100644 --- a/docs/extending/sandy-migration/index.html +++ b/docs/extending/sandy-migration/index.html @@ -17,7 +17,7 @@ - + @@ -29,7 +29,7 @@ These will provide a more consistent user experience, usually provide better UX and they support dark mode! Roughly speaking this typically involves replacing all imported components with their modern counterpart.

For Sandy plugins, components can be found here:

  • Interactive data displaying components are exposed from flipper-plugin: DataTable (for tables), DataInspector (for JSON trees) and ElementInspector (for element trees).
  • flipper-plugin also provides the primitives to organise the Layout of the plugin.
  • Practically all other, more generic components are provided by Ant Design, a proven mature open source component library, which is much richer than the components that are offered from flipper.

In Sandy, the layout is typically built by using a combination of the following:

  • Layout.Top (or .Right, .Left, .Bottom), which divides all available space in a fixed and dynamic section
  • Layout.Scrollable, which takes all available space and provides scrollbars if its content is still greater,
  • Layout.Container which organizes paddings, borders and spacing between elements etc.

Generally, it's recommended not to use margins; use padding and gap instead.

Ideally, use theme.spacing to get standard numbers for margins and paddings instead of hard-coded numbers. This will help with achieving consistency in look and feel.

Design resources

There are three important resources to check for documentation on the components available:

  1. Flipper style guide - a general overview of the Flipper design system that demonstrates colors, typography and creating layouts including some examples.
  2. Ant Design component overview
  3. API reference documentation for the components provided by flipper-plugin

Old and new components

For conversion, the following table maps the old components to the new ones:

Old flipper componentNew componentProviding packageNotes
DetailsSidebarDetailsSidebarflipper-pluginas-is
SidebarLayout.Top (or .Right, .Left, .Bottom)flipper-pluginSet the resizable flag to allow the user to resize the pane.
FlexColumn / Pane / ViewLayout.Containerflipper-pluginUse the gap property to provide some spacing between the children!
FlexRowLayout.Horizontalflipper-pluginUse the gap property to provide some spacing between the children!
ScrollableLayout.ScrollContainerflipper-plugin
LinkTypography.Linkantd
Text / HeadingTypography.Text / Typography.Titleantd
ButtonButtonantd
GlyphIconantd
ManagedDataTableDataTableflipper-pluginRequires state to be provided by a createDataSource
ManagedDataInspector / DataInspectorDataInspectorflipper-plugin
ManagedElementInspector / ElementInspectorElementInspectorflipper-plugin
PanelPanelflipper-plugin
Tabs / TabTabs / Tab`flipper-pluginNote that Tab's title property is now called tab.

Most other components, such as select elements, tabs, and date-pickers can all be found in the Ant documentation.

Theming & custom styled components

Creating your own components / styling using styled is still supported. But ideally, you should need custom styled components a lot less!

Since Sandy plugins are expected to support dark mode, (use the settings pane to quickly toggle), it's recommended not to use hard-coded colors. Instead, use one of the semantic colors that are provided through the theme object that can be imported from flipper-plugin.

Ideally, there should be no hard-coded colors anymore either, and little need to use width: 100% / height: 100% anywhere, as needing those typically signals a layout issue.

Tip

It's recommended to keep components as much as possible outside the entry file, as components defined outside the index.tsx file will benefit from fast refresh.

Wrapping up

This step of the process is completed as soon as there are no imports from the flipper package anymore. Don't forget to remove flipper from the peerDependencies in the package.json section if present.

If you have any questions, feel free to reach out to the Flipper Support Workplace group.

- + \ No newline at end of file diff --git a/docs/extending/style-guide/index.html b/docs/extending/style-guide/index.html index ebd911c6293..2bac94c6f3a 100644 --- a/docs/extending/style-guide/index.html +++ b/docs/extending/style-guide/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/docs/extending/styling-components/index.html b/docs/extending/styling-components/index.html index da0c0dfc044..fba8c7bb8da 100644 --- a/docs/extending/styling-components/index.html +++ b/docs/extending/styling-components/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Styling Components

Flipper ships with its own design system that is based on Ant Design. In general, custom styling should be needed rarey, as Ant Design provides a very extensive set of components.

To build plugin layout and data visualization Flipper ships with an additional set of components through the flipper-plugin package. The list of available additional components can be found in the API Reference and are further documented in the Style Guide.

In case you still need custom-styled components, you can use emotion to style your components. For more details on how this works, please refer to emotion's documentation. Emotion's Styled Components approach enables you to extend our and Ant's built-in components.

Basic tags

For basic building blocks (views, texts, and so on), you can use the styled object, as shown below:

import {styled} from 'flipper-plugin';

const MyView = styled.div({
fontSize: 10,
color: colors.red
});
const MyText = styled.span({ ... });
const MyImage = styled.img({ ... });
const MyInput = styled.input({ ... });

Extending Flipper Components

In some cases, it's required to customize Ant or Flipper's components in some way. For example, changing colors, alignment, or wrapping behavior. Flippers components can be wrapped using the styled function which allows adding or overwriting existing style rules.

import {Layout, styled} from 'flipper-plugin';

const Container = styled(Layout.Container)({
alignItems: 'center',
});

function MyComponent {
return <Container>...</Container>;
}

CSS

The CSS-in-JS object passed to the styled components takes any CSS rule but uses came-cased keys for the properties. Pixel-values can be numbers. Any other values need to be strings.

The style object can also be returned from a function for dynamic values. Props can be passed to the styled component using React, as follows:

const MyView = styled.div(
props => ({
fontSize: 10,
color: => (props.disabled ? 'red' : 'black'),
})
);

// usage
<MyView disabled />

Pseudo-classes can be as follows:

'&:hover': {color: 'red'}`

Children can be matched by using normal CSS selectors. This makes it possible to customize Ant components as well:

'.ant-btn-primary': {color: 'yellow'}

Colors

The theme module contains all standard colors used by Flipper. All available colors can be previewed in the style guide. The colors exposed here handles dark mode automatically, so it's recommended to use those colors over hardcoded ones.

import {theme} from 'flipper-plugin'
- + \ No newline at end of file diff --git a/docs/extending/supporting-layout/index.html b/docs/extending/supporting-layout/index.html index 98dffd716aa..0eeda000fcc 100644 --- a/docs/extending/supporting-layout/index.html +++ b/docs/extending/supporting-layout/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Implementing Layout Inspection

To enable the Flipper Layout Inspector on a new platform, just implement a client plugin with id Inspector that implements the interface detailed in this page.

note

The Flow syntax is used to specify this JSON API.

Node

Node is the core data type of the layout inspector. The Flipper desktop plugin visualizes a tree of nodes with associated data and attributes. Any UI or data model which can be modeled as a tree of nodes can be inspected using the layout inspector. Data associated with the nodes can also be edited.

type NodeId = string;

type InspectorValue = {
__type__: 'auto' | 'text' | 'number' | 'boolean' | 'enum' | 'color',
__mutable__: boolean,
value: number | string | boolean,
};

type Node = {
id: NodeId,
name: string,
data: ?{string: Object},
children: Array<NodeId>,
attributes: ?Array<{name: string, value: string}>,
decoration: ?string,
};

Node Parameters

Following is a description of the parameters used in the above node:

  • id - a stable, globally unique, node identifier.
  • name - the user-facing identifier for this node. It does not need to be unique. Typically, the class name of the node is used as the node's name.
  • data - a set of named JSON objects representing data associated with the node. This 'data' is rendered as immutable, by default, to the user of the plugin but can be made mutable by wrapping any value in a 'InspectorValue' with the __mutable__ attribute set to true.
    • The 'InspectorValue' can also be used to change the parsed type of the value, such as parsing a number as a color to show the value in a color picker.
  • children - a list of identifiers pointing to children of this node. This is a list of identifiers instead of a list of nodes to allow nodes to be lazily fetched and instantiated.
  • attributes- a list of key-value pairs, which are displayed alongside the name in the Layout Inspector.
  • decoration - a string identifying the optional icon used to decorate a node in the Layout Inspector. Adding new decoration options requires adding an icon file to the Sonar desktop app. Currently, ComponentKit and Litho decorations are supported.

Plugin Interface

interface ClientLayoutPlugin {
Node getRoot();
GetNodesResponse getNodes({ids: Array<NodeId>});
GetAllNodesResponse getAllNodes();
void setData({id: NodeId, path: Array<string>, value: any});
void setHighlighted({id: ?NodeId});
void setSearchActive({active: boolean});
GetSearchResultsResponse getSearchResults({query: string});
};

interface DesktopLayoutPlugin {
void invalidate({id: NodeId});
void select({path: Array<NodeId>});
};

type GetNodesResponse = {
elements: Array<Node>
};
type GetAllNodesResponse = {
elements: Array<Node>,
rootElement: NodeId
};
type GetSearchResultsResponse = {
results: ?SearchResultNode,
query: string
};

type SearchResultNode = {
id: NodeId,
isMatch: boolean,
element: Node,
children: ?Array<SearchResultNode>
}
type NodeId = string;

Interface methods

  • getRoot - returns the root node of your hierarchy. This is the entry point of Flipper's traversal of your layout.
  • getNodes - maps a set of Node Ids to their corresponding nodes. This call is used to, among other things, query the children of a node.
  • getAllNodes - similar to 'getNodes', this should return all nodes in the current layout tree. Ordinarily, nodes are requested lazily, however this exists for taking snapshots of the current state.
  • setData - set the data of an mutable data object returned as part of the data field of a node:
    • The id parameter identifies the node.
    • The path parameter is an index path into an object, such as ['bounds', 'left'].
    • The value parameter is a value of appropriate type to be used as an override.
  • setHighlighted - marks a node as highlighted. It is expected that implementations add a colored overlay to the node identified by id on screen, so that as the user browses the layout tree in Flipper, they can easily see on the client display the nodes with which they are interacting. Passing a null id parameter removes the current highlight without highlighting a new node.
  • setSearchActive - the user has clicked on the crosshair button in Sonar. This feature allows the user to click on an element in the client UI to cause Flipper to highlight the corresponding node in the layout tree. A colored overlay should be shown over the whole screen until setSearchActive is called with active: false. While setSearchActive is true. Clicking an element in the client UI should trigger a select call to the Flipper desktop, with the path of ids from root to selected node, for example, select(['node1', 'node6', 'node65']) to select a grandchild of node1.
  • getSearchResults - executes a query on all nodes in the tree and returns a subtree of the layout tree that contains all matching nodes and those on the path from root. A parent that does not itself match the query but exists on the path to a node that does, should have the attribute isMatch: false and only the matching nodes should have isMatch: true. Nodes not on the path from root to a match need not be included in the returned tree. Be careful not to confuse this method with the unrelated setSearchActive, which unfortunately shares a similar name.
note

Whenever a node or subtree changes, it is expected that the client sends a invalidate command to the desktop app over the active connection. This invalidates the cache of the subtree anchored by the node with the given id.

- + \ No newline at end of file diff --git a/docs/extending/testing-rn/index.html b/docs/extending/testing-rn/index.html index 8879d40d191..1cfbf896687 100644 --- a/docs/extending/testing-rn/index.html +++ b/docs/extending/testing-rn/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Testing React Native Changes in the Sample App

When making changes to the React Native integration, it can be helpful to test them directly in the ReactNativeFlipperExample we provide in the repository. However, the app builds against a production release of Flipper and not the version checked in in the repository.

In order to test against the changes you have made, you need to publish a Flipper release locally.

Publishing a Local Release

First, create a unique version number. This step is optional but is helpful to prevent accidentally testing against the wrong version.

In the top-level gradle.properties, change the version:

...
# POM publishing constants
VERSION_NAME=0.44.99-SNAPSHOT # Change this one.
GROUP=com.facebook.flipper
...

Now, run ./gradlew installArchives -PRELEASE_SIGNING_ENABLED=false in the root of the repository. This will place the artifact files in your local ~/.m2/repository folder.

Using the new Release

Usually, you need to change your project to also pick up files in mavenLocal(). As this is already done, you can directly change the FLIPPER_VERSION to the previously used identifier in react-native/ReactNativeFlipperExample/android/gradle.properties:

# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.30.2
FLIPPER_VERSION=0.44.99-SNAPSHOT

Now run yarn android to rebuild, install and launch the RN sample app.

To test further changes, rerun the ./gradlew installArchives -PRELEASE_SIGNING_ENABLED=false and yarn android steps.

Full Diff

For a quick overview, here are the two changes you need to make:

diff --git a/gradle.properties b/gradle.properties
index 1ccd002a..100a7169 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,7 +4,7 @@
# LICENSE file in the root directory of this source tree.

# POM publishing constants
-VERSION_NAME=0.44.1-SNAPSHOT
+VERSION_NAME=0.44.99-SNAPSHOT
GROUP=com.facebook.flipper
POM_URL=https://github.com/facebook/flipper
POM_SCM_URL=https://github.com/facebook/flipper.git
diff --git a/react-native/ReactNativeFlipperExample/android/gradle.properties b/react-native/ReactNativeFlipperExample/android/gradle.properties
index 495c12e8..bc815d0e 100644
--- a/react-native/ReactNativeFlipperExample/android/gradle.properties
+++ b/react-native/ReactNativeFlipperExample/android/gradle.properties
@@ -30,4 +30,4 @@ android.useAndroidX=true
android.enableJetifier=true

# Version of flipper SDK to use with React Native
-FLIPPER_VERSION=0.30.2
+FLIPPER_VERSION=0.44.99-SNAPSHOT

- + \ No newline at end of file diff --git a/docs/extending/testing/index.html b/docs/extending/testing/index.html index 502614e42d3..734f3e5e399 100644 --- a/docs/extending/testing/index.html +++ b/docs/extending/testing/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Testing

Developer tools are only used if they work. Testing is important as it discovers defects/bugs and improves the quality, reliability and functionality of software. This page details the Flipper APIs that can be used to effectively test plugins.

Writing tests

This section covers desktop plugins and client plugins.

Desktop plugins

Flipper uses Jest as a unit testing framework.

Writing unit tests for Flipper Desktop plugins is covered in detail in the Building a Desktop Plugin tutorial.

The flipper-plugin package provide several test utilities to make testing more convenient.

Client plugins

Start by creating your first test file in this directory MyFlipperPluginTest.java. In the test method body, is the plugin to be tested as well as a FlipperConnectionMock.

The following example asserts that the plugin's connected status is what is expected:

@RunWith(RobolectricTestRunner.class)
public class MyFlipperPluginTest {

@Test
public void myTest() {
final MyFlipperPlugin plugin = new MyFlipperPlugin();
final FlipperConnectionMock connection = new FlipperConnectionMock();

plugin.onConnect(connection);
assertThat(plugin.connected(), equalTo(true));
}
}

There are two mock classes that are used to construct tests: FlipperConnectionMock and FlipperResponderMock. Together these can be used to write very powerful tests to verify the end-to-end functionality of your plugin.

For example, you can test if, for a given incoming message, your plugin responds as expected:

@Test
public void myTest() {
final MyFlipperPlugin plugin = new MyFlipperPlugin();
final FlipperConnectionMock connection = new FlipperConnectionMock();
final FlipperResponderMock responder = new FlipperResponderMock();

plugin.onConnect(connection);

final FlipperObject params = new FlipperObject.Builder()
.put("phrase", "flipper")
.build();
connection.receivers.get("myMethod").onReceive(params, responder);

assertThat(responder.successes, hasItem(
new FlipperObject.Builder()
.put("phrase", "ranos")
.build()));
}

Running (Flipper) tests

This section covers running tests on the Flipper Desktop and with the Flipper SDK.

Flipper Desktop

Run yarn jest or yarn jest --watch in the desktop directory of your Flipper checkout.

Flipper SDK

Android (Java)

Gradle

In the root directory of the checkout:

./gradlew android:test

React Native

For details, see the Testing React Native Changes page.

- + \ No newline at end of file diff --git a/docs/features/index.html b/docs/features/index.html index e4c10c03a19..e13896addf7 100644 --- a/docs/features/index.html +++ b/docs/features/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Introduction

Flipper itself only provides the architectural platform. What makes it useful are the plugins built on top of it, such as Logs, Layout Inspector and Network Inspector.

Plugins can be tailored to your business logic and the use-cases you have in your app. Flipper is shipped with a couple of built-in all-purpose plugins, but you're encouraged to build your own (see below). Each plugin needs to be enabled individually.

Plugins

Build your own plugin

The Flipper desktop app and the mobile native SDK establish a connection that is used to send data to and from the device. Flipper does not make any restrictions on what kind of data is being sent. This enables a lot of different use-cases where you want to better understand what is going inside your app. For example, you can visualize the state of local caches, events happening or trigger actions on your app from the desktop.

If there is no plugin that does exactly what you want, you can build your own plugin tailored to your needs. A plugin always consists of the native implementation sending and receiving data and the desktop plugin visualizing data: the native implementations are written in Java, Objective-C, or C++, the desktop UI is written in React.

To learn more and build your own plugin, see the Creating Plugins section of the Flipper Docs.

- + \ No newline at end of file diff --git a/docs/features/plugins/crash-reporter/index.html b/docs/features/plugins/crash-reporter/index.html index f1020a2189b..3d356146156 100644 --- a/docs/features/plugins/crash-reporter/index.html +++ b/docs/features/plugins/crash-reporter/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Crash Reporter Plugin

See setup instructions for the Crash Reporter plugin

The Crash Reporter plugin shows a notification in Flipper whenever an app crashes.

For Android, clicking on the 'Open in Logs' button jumps to the relevant row in the Logs plugin containing the crash information, as shown in the following screenshots.

UI
- + \ No newline at end of file diff --git a/docs/features/plugins/databases/index.html b/docs/features/plugins/databases/index.html index 7d1a5f6ef27..d9ae0236e79 100644 --- a/docs/features/plugins/databases/index.html +++ b/docs/features/plugins/databases/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Databases Plugin

See setup instructions for the Databases plugin

The Databases plugin provides Developers with read-write access to databases.

Plugin functionality

The plugin provides the following functionality:

Examine table structure

The following screenshot shows the structure of the 'ranking' table.

Databases Plugin 1

Execute queries

The following screenshot shows the recordset resulting from execution of the 'statusranking' query.

Databases Plugin 2
- + \ No newline at end of file diff --git a/docs/features/plugins/device-logs/index.html b/docs/features/plugins/device-logs/index.html index 02e735e4f84..dbb64f6984e 100644 --- a/docs/features/plugins/device-logs/index.html +++ b/docs/features/plugins/device-logs/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Logs Plugin

The Logs plugin shows device logs without any additional setup. This is a device plugin, which means it's not tied to any specific app and there is no additional setup needed to see the logs.

The following screenshot shows an example of a device log obtained via the Logs plugin.

Logs plugin

Plugin functionality

Filtering

The search bar can be used to search for logs and act as a filter for certain types.

From the context menu on the table headers, you can show additional information, such as timestamp, PID or TID.

Clicking on a tag, PID or TID in the table filters only for logs with the same value.

Expression Watcher

The Expression Watcher in the sidebar can be used to 'watch' for certain logs to happen and count how often they occur. An expression can be a simple string, or a regular expression, matched against the logs.

When the notify checkbox is enabled, Flipper sends notifications once the log is being processed. This lets you know when the 'watcher' triggered, even if Flipper is in the background.

- + \ No newline at end of file diff --git a/docs/features/plugins/fresco/index.html b/docs/features/plugins/fresco/index.html index 3aff2bb723b..2cef2c4335d 100644 --- a/docs/features/plugins/fresco/index.html +++ b/docs/features/plugins/fresco/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Images Plugin

See setup instructions for the Images plugin

The Images plugin enables you to inspect what images were fetched, where they are coming from, and selectively clear caches.

Currently, the plugin supports Fresco as the backend.

Images plugin

Cache Inspector

Images are grouped by the different caching layers they are stored in. The current fill rate of the cache is shown and you can choose to selectively clear caches.

Attribution

Images can be annotated with attributes that can help to determine the context in which an image was loaded and displayed. You can use that information to filter by a particular surface or only inspect images that are in the critical path of your application (such as during a cold start).

Leak Tracking

Dealing with large resources can require special APIs to be used that circumvent usual garbage collection. The plugin enables the tracking of CloseableReferences for Fresco on Android that weren't properly closed, which can help you improve the performance of your app.

- + \ No newline at end of file diff --git a/docs/features/plugins/inspector/index.html b/docs/features/plugins/inspector/index.html index d7558e10bc6..380d891ebc1 100644 --- a/docs/features/plugins/inspector/index.html +++ b/docs/features/plugins/inspector/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Layout Plugin

See setup instructions for the Layout plugin

The Layout Inspector is useful for a wide variety of debugging scenarios. You can inspect what views the hierarchy is made up of as well as what properties each view has; this is incredibly useful when debugging issues with your product.

In addition to Flipper, the Layout tab supports Litho and ComponentKit components; it integrates with these frameworks to present components in the hierarchy just as if they were native views, exposing all the layout properties, props, and state of the components. The Layout Inspector is further extensible to support other UI frameworks.

If you hover over a view or component in Flipper (as show in the following screenshot), the corresponding item in your app is highlighted. This is perfect for debugging the bounds of your views and making sure you have the correct visual padding.

Layout plugin

Quick edits

In addition to enabling you to view the hierarchy and inspect each item's properties, the Layout Inspector also enables you to edit almost everything, such as layout attributes, background colors, props, and state. This allows you to quickly tweak paddings, margins, and colors until you are happy with them, all without re-compiling. This can save you many hours implementing a new design.

Target mode

You can enable 'target mode' by clicking on the crosshairs icon (see screenshot, above). After which, you can touch any view on the device and the Layout Inspector will jump to the correct position within your layout hierarchy.

tip

Target mode also works with Talkback running.

Accessibility mode (Android-only)

You can enable 'accessibility mode' by clicking on the accessibility icon (see screenshot, above). This shows the accessibility view hierarchy next to the normal hierarchy. In the hierarchy, the currently accessibility-focused view is highlighted in green, and any accessibility-focusable elements have a green icon next to their name. The hierarchy's context menu also allows you to focus accessibility services on certain elements. When selecting an element in one hierarchy, the corresponding element in the other will also be highlighted. The hierarchies expand and collapse in sync and searching through the main hierarchy works in accessibility mode as well.

When accessibility mode is enabled, the sidebar shows special properties that are used by accessibility services to determine their functionality. This includes items such as content-description, clickable, focusable, and long-clickable, among others.

Talkback

The accessibility mode sidebar also includes a panel with properties derived specifically to show Talkback's interpretation of a view (with logic ported over from Google's Talkback source). While generally accurate, this is not guaranteed to be accurate for all situations. It is always better to turn Talkback on for verification.

- + \ No newline at end of file diff --git a/docs/features/plugins/leak-canary/index.html b/docs/features/plugins/leak-canary/index.html index e4071ba8b7d..044d72dd2d6 100644 --- a/docs/features/plugins/leak-canary/index.html +++ b/docs/features/plugins/leak-canary/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

LeakCanary Plugin

See setup instructions for the LeakCanary plugin

The LeakCanary plugin provides Developers with Flipper support for LeakCanary, an open source memory leak detection library.

Leaks detected by LeakCanary appear automatically in Flipper. Each leak displays a hierarchy of objects, beginning with the garbage collector root and ending at the leaked class. Selecting any object in this list displays contents of the object's various fields.

- + \ No newline at end of file diff --git a/docs/features/plugins/navigation/index.html b/docs/features/plugins/navigation/index.html index aa557007353..c5163831fe2 100644 --- a/docs/features/plugins/navigation/index.html +++ b/docs/features/plugins/navigation/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Navigation Plugin

See setup instructions for the Navigation plugin

The Navigation Plugin enables users to quickly navigate to deep links within their mobile applications to help speed up the development cycle. The plugin is designed to integrate easily within your existing navigation framework or as a standalone tool.

Users can bookmark deep-links and jump to them via the button in the toolbar, as shown in the following screenshot. Navigation events within the app can also be logged to Flipper, which enables the user to view past navigation events and jump straight to them or export the navigation events for reporting.

- + \ No newline at end of file diff --git a/docs/features/plugins/network/index.html b/docs/features/plugins/network/index.html index 452ef8a21b7..83e9a582521 100644 --- a/docs/features/plugins/network/index.html +++ b/docs/features/plugins/network/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Network Plugin

See setup instructions for the Network plugin

The Network plugin provides the Network Inspector, which is used to inspect outgoing network traffic in your apps. You can easily browse all requests being made and their responses. The plugin also supports gzipped responses.

All requests sent from the device are listed in the plugin. Clicking on a request shows details such as headers and body. You can filter the table for domain, method or status by clicking on the corresponding value in the table.

The following screenshot shows the Network Inspector in action.

Network plugin
- + \ No newline at end of file diff --git a/docs/features/plugins/preferences/index.html b/docs/features/plugins/preferences/index.html index 180a8609f41..f2f2336c9ce 100644 --- a/docs/features/plugins/preferences/index.html +++ b/docs/features/plugins/preferences/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Shared Preferences Viewer Plugin

See setup instructions for the Shared Preferences Viewer plugin

The Shared Preferences Viewer plugin enables you to easily inspect and modify the data contained within your app's shared preferences, as shown in the following screenshot.

Shared Preferences Plugin

All changes to the given shared preference file automatically appear in Flipper.

You may also edit the values in Flipper and have them synced to your device. This can be done by clicking on the value of the specific key you wish to edit, editing the value and then pressing enter.

- + \ No newline at end of file diff --git a/docs/features/plugins/sandbox/index.html b/docs/features/plugins/sandbox/index.html index 874942c27b1..17bbd8636b9 100644 --- a/docs/features/plugins/sandbox/index.html +++ b/docs/features/plugins/sandbox/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Sandbox Plugin

See setup instructions for the Sandbox plugin

The Sandbox plugin enables Developers to test changes in their apps by pointing them to a sandbox environment. It provides a simple UI to set and modify the URL to a development host that acts as a sandbox directly on the desktop, which prevents you from entering potentially long and complicated URLs inside your app.

- + \ No newline at end of file diff --git a/docs/features/plugins/ui-debugger/index.html b/docs/features/plugins/ui-debugger/index.html index c35341175b4..7249524bbde 100644 --- a/docs/features/plugins/ui-debugger/index.html +++ b/docs/features/plugins/ui-debugger/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

UI Debugger Plugin

The UIDebugger is a replacement for the Layout inspector. It streams the full hierarchy of the running app to flipper desktop in near real time. We display a 2D visualization with all of your view and component bounds overlayed on top. You can focus on a particular view or component from the context menu. Additionally, you can pause incoming updates to focus on a particular frame.

You can inspect what views the hierarchy is made up of as well as what properties each view has; this is useful when debugging issues with your product.

We currently support the following platforms and frameworks:

It integrates with these frameworks to present components in the hierarchy just as if they were native views, exposing all the layout properties, props, and state of the components.

UIDebugger
- + \ No newline at end of file diff --git a/docs/features/react-native/index.html b/docs/features/react-native/index.html index c85adda37a6..dc0762d4698 100644 --- a/docs/features/react-native/index.html +++ b/docs/features/react-native/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

React Native Support

React Native + React DevTools

Meta's React Native and Developer Tooling teams work in close collaboration to ensure Flipper offers top-notch out-of-the-box value for React Native development.

note

Integration between React Native and Flipper is enabled out of the box in React Native version 0.62 and higher.

For the setup instructions for React Native, see the Desktop App page.

The following 37-minute video provides interesting information on 'Flipper: the extensible DevTool Platform for React Native`.


Device type: React Native

In Flipper, the dedicated device type, 'React Native', connects to a locally running Metro instance to interact with your React Native app. This device is detected as soon as you fire up a Metro instance by running yarn run ios or yarn run android in your project.

If Metro is connected, two new buttons appear in Flipper’s main toolbar: 'Reload' and 'Open Dev Menu': both do exactly as their name suggests. The 'React Native' device feature two plugins out of the box: 'Logs' and 'React DevTools', as shown in the following screenshot.

React Native Action Buttons and Logs

The React DevTools allows you to inspect the component tree and tune the props and state of your React components.

The Logs plugins allow you to search, filter and place watch expressions on your logging output. This offers a much richer way to interact with your logs compared to the terminal output of Metro!

Native plugins for React Native

Beyond the React Native-specific Flipper plugins described above, with Flipper you also inherit the plugin eco-system that exists for native Android and iOS apps. This means that you are able to use plugins that are also aimed at native apps for your React Native app.

Example plugins include:

  • Device logs
  • Device crash reporter
  • Inspecting network requests
  • Inspecting app local databases
  • Inspecting device preferences
  • Inspecting cached images
  • Inspecting native layout elements

Writing JavaScript plugins for React Native + Flipper

One of the advantages of Flipper is its extensibility. Many teams across Meta already have written their own one-off plugins that help with analysing very specific use cases. Writing plugins for Flipper doesn't require any native code, as the Flipper SDK is exposed directly to JavaScript through the react-native-flipper package.

The following screenshot shows an example Flipper plugin, where a game of Tic Tac Toe uses Flipper and some emulators.

Tic Tac Toe example plugin

If you'd like to build a specific (or generic) extension for Flipper, take a look at the following pointers:

note

Plugins for Flipper can be distributed through NPM so sharing them is trivial.

Community React Native plugins for Flipper

The React Native community has also started to build plugins for Flipper.

Reactotron's Flipper plugin is an example of a standalone React Native desktop app that is ported to work as a Flipper plugin. For more information, see the Better React Native Debugging with Reactotron in Flipper web page.

If you've got your own Flipper plugin for React Native that you'd like to advertise, please send the Litho team a pull request!

- + \ No newline at end of file diff --git a/docs/features/share-flipper-data/index.html b/docs/features/share-flipper-data/index.html index 90d7dfe325a..7e75c4a0fde 100644 --- a/docs/features/share-flipper-data/index.html +++ b/docs/features/share-flipper-data/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Share Flipper Data

Flipper's diagnostic and profiling data is often used for debugging. However, sometimes you can't solve the problem on your own and need some help from your colleagues. In such cases, rather than just sending screenshots, you can share the data you are seeing in Flipper (such as logs, layout hierarchy, network requests, and other relevant objects) with your colleague, which helps them to get the bigger picture of why something doesn't work as expected.

Export Flipper data

To export Flipper data in a .flipper file, take the following steps:

  1. An active device needs to be connected to Flipper, so ensure you've selected the device you want to export in Flipper's device dropdown.

    selectedDevice
  2. Select the 'More' button on the toolbar, then 'Export Flipper file' and save it where you like.

The exported data can now be shared with your colleagues.

caution

Bear in mind that the file will include all the data available to the plugins (such as access tokens in recorded network requests)!

Import Flipper data

Opening a .flipper file imports all the data and allows you to use Flipper as if the device was connected. However, the device is marked as 'offline' (see the following screenshot). Since the device is not present, this means that while you are able to see all the data, you can't interact with it (for example, changing a view's background color).

importedDevice

For advanced users, Flipper also provides a URL handler to import data. For example, linking to flipper://import/?url={LINK_TO_FLIPPER_FILE} will launch Flipper and display the downloaded data.

- + \ No newline at end of file diff --git a/docs/features/virtual-devices/index.html b/docs/features/virtual-devices/index.html index b720cf0d9db..d831c18125a 100644 --- a/docs/features/virtual-devices/index.html +++ b/docs/features/virtual-devices/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Virtual devices

You can use flipper to launch iOS simulators and Android emulators.

Select "Virtual devices" from the flipper menu

From the opened modal you can select available virtual devices.

Select the one you want and it will shortly open and connect to Flipper.

You can also "favorite" your most used devices to always display them in the top of the list.

- + \ No newline at end of file diff --git a/docs/getting-started/android-native/index.html b/docs/getting-started/android-native/index.html index e033557bfd0..9fd28341536 100644 --- a/docs/getting-started/android-native/index.html +++ b/docs/getting-started/android-native/index.html @@ -17,7 +17,7 @@ - + @@ -26,9 +26,9 @@

Adding Flipper to Android apps with Gradle

To set up Flipper for Android, you need to add the necessary dependencies to your app, initialize the Flipper client and enable the plugins you want to use. Optionally, you can hook up the diagnostics Activity to help you troubleshoot connection issues.

Dependencies

Flipper is distributed via Maven Central: add the dependencies to your build.gradle file.

You should also explicitly depend on SoLoader instead of relying on transitive dependency resolution, which is getting deprecated -with Gradle 5.

There is a 'no-op' implementation of some oft-used Flipper interfaces, which you can use to make it easier to strip Flipper from your release builds:

repositories {
mavenCentral()
}

dependencies {
debugImplementation 'com.facebook.flipper:flipper:0.259.0'
debugImplementation 'com.facebook.soloader:soloader:0.10.5'

releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.0'
}
danger

The flipper-noop package provides a limited subset of the APIs provided by the flipper package and does not provide any plugin stubs. -It's recommended that you keep all Flipper instantiation code in a separate build variant to ensure it doesn't accidentally make it into your production builds.

To see how to organise your Flipper initialization into debug and release variants, check this sample app.

Alternatively, have a look at the third-party flipper-android-no-op repository, which provides empty implementations for several Flipper plugins.

Application setup

Now you can initialize Flipper in your Application's onCreate method, which involves initializing SoLoader (for loading the C++ part of Flipper) and starting a FlipperClient.

import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.core.FlipperClient
import com.facebook.flipper.plugins.inspector.DescriptorMapping
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin

class MyApplication : Application {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
client.start()
}
}
}

Diagnostics

It's recommended that you add the following activity to the manifest, which can help diagnose integration issues and other problems:

<activity android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true"/>

Android snapshots

note

Android snapshot releases are published directly off main.

You can get the latest version by adding the Maven Snapshot repository to your sources and pointing to the most recent -SNAPSHOT version.

repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

dependencies {
debugImplementation 'com.facebook.flipper:flipper:0.259.1-SNAPSHOT'
debugImplementation 'com.facebook.soloader:soloader:0.10.5'

releaseImplementation 'com.facebook.flipper:flipper-noop:0.259.1-SNAPSHOT'
}

Enabling plugins

Finally, you need to add plugins to your Flipper client.

Above, the Layout Inspector plugin has been added to get you started. See the Network Plugin and Layout Inspector Plugin pages for information on how to add them, and also enable Litho or ComponentKit support.

For examples of integrating other plugins, take a look at the sample apps in the GitHub repo.

Issues or questions

If you encounter any issues or have any questions, refer to the Troubleshooting section.

- +with Gradle 5.

There is a 'no-op' implementation of some oft-used Flipper interfaces, which you can use to make it easier to strip Flipper from your release builds:

repositories {
mavenCentral()
}

dependencies {
debugImplementation 'com.facebook.flipper:flipper:0.260.0'
debugImplementation 'com.facebook.soloader:soloader:0.10.5'

releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.0'
}
danger

The flipper-noop package provides a limited subset of the APIs provided by the flipper package and does not provide any plugin stubs. +It's recommended that you keep all Flipper instantiation code in a separate build variant to ensure it doesn't accidentally make it into your production builds.

To see how to organise your Flipper initialization into debug and release variants, check this sample app.

Alternatively, have a look at the third-party flipper-android-no-op repository, which provides empty implementations for several Flipper plugins.

Application setup

Now you can initialize Flipper in your Application's onCreate method, which involves initializing SoLoader (for loading the C++ part of Flipper) and starting a FlipperClient.

import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.core.FlipperClient
import com.facebook.flipper.plugins.inspector.DescriptorMapping
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin

class MyApplication : Application {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
client.start()
}
}
}

Diagnostics

It's recommended that you add the following activity to the manifest, which can help diagnose integration issues and other problems:

<activity android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true"/>

Android snapshots

note

Android snapshot releases are published directly off main.

You can get the latest version by adding the Maven Snapshot repository to your sources and pointing to the most recent -SNAPSHOT version.

repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

dependencies {
debugImplementation 'com.facebook.flipper:flipper:0.260.1-SNAPSHOT'
debugImplementation 'com.facebook.soloader:soloader:0.10.5'

releaseImplementation 'com.facebook.flipper:flipper-noop:0.260.1-SNAPSHOT'
}

Enabling plugins

Finally, you need to add plugins to your Flipper client.

Above, the Layout Inspector plugin has been added to get you started. See the Network Plugin and Layout Inspector Plugin pages for information on how to add them, and also enable Litho or ComponentKit support.

For examples of integrating other plugins, take a look at the sample apps in the GitHub repo.

Issues or questions

If you encounter any issues or have any questions, refer to the Troubleshooting section.

+ \ No newline at end of file diff --git a/docs/getting-started/index.html b/docs/getting-started/index.html index 70889de6a3a..75418a8da00 100644 --- a/docs/getting-started/index.html +++ b/docs/getting-started/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Desktop App

Flipper helps you debug in the following environments:

  • Android and iOS.
  • Web apps running in an emulator/simulator.
  • Connected physical development devices.
  • Your browser.

Flipper consists of two parts:

  • The desktop app.
  • The native mobile SDKs for Android and iOS, the client for JavaScript, or even a third-party client you could implement yourself or find on the web.

Once you start Flipper and launch an emulator/simulator or connect a device, you'll start to see the device logs (and any other device-level plugins that work with your device). Currently, there are no plugins available for web apps.

To see app-specific data, you need to integrate the Flipper SDK into your app (see the 'Adding Flipper to your app' within the 'Getting Started' section of the SideBar).

Installation

note

The desktop part of Flipper doesn't need a setup. Simply download the latest build for Mac, Linux or Windows and launch it.

If you're on macOS, you can run brew install --cask flipper to let homebrew manage installation and upgrades (simply run brew upgrade to upgrade when a new version is released, although it might take a few hours up to a day for the package to be upgraded on homebrew).

To work properly with mobile apps, Flipper requires the following:

  • Working installation of Android development tools
  • [Where applicable] Working installation of iOS development tools
  • OpenSSL binary on your $PATH. A compatible OpenSSL for Windows can be downloaded from slproweb.com or from Chocolatey with choco install openssl.

If you are hacking a JS app, you should be good to go without any extra dependencies installed.

Information

[Experimental] Alternatively, it is possible to run a browser based version of Flipper directly from NPM by using npx flipper-server.

Troubleshooting

If you run into problems, take a look at the Troubleshooting section. Failing that, have a look at GitHub Issues.

- + \ No newline at end of file diff --git a/docs/getting-started/ios-native/index.html b/docs/getting-started/ios-native/index.html index b00ba3765a7..e1f9767bcd9 100644 --- a/docs/getting-started/ios-native/index.html +++ b/docs/getting-started/ios-native/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Adding Flipper to Generic iOS Apps

note

Swift and Objective-C are supported for Flipper with CocoaPods as build and distribution mechanism.

CocoaPods

The following configuration assumes CocoaPods 1.9+:

project 'MyApp.xcodeproj'
flipperkit_version = '0.250.0'

target 'MyApp' do
platform :ios, '10.0'

# It is likely that you'll only want to include Flipper in debug builds,
# in which case you add the `:configuration` directive:
pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
# ...unfortunately at this time that means you'll need to explicitly mark
# transitive dependencies as being for debug build only as well:
pod 'Flipper-DoubleConversion', :configuration => 'Debug'
pod 'Flipper-Folly', :configuration => 'Debug'
pod 'Flipper-Glog', :configuration => 'Debug'
pod 'Flipper-PeerTalk', :configuration => 'Debug'
pod 'CocoaLibEvent', :configuration => 'Debug'
pod 'boost-for-react-native', :configuration => 'Debug'
pod 'OpenSSL-Universal', :configuration => 'Debug'
pod 'CocoaAsyncSocket', :configuration => 'Debug'
# ...except, of course, those transitive dependencies that your
# application itself depends, e.g.:
pod 'ComponentKit', '~> 0.31'

# If you use `use_frameworks!` in your Podfile,
# uncomment the below $static_framework array and also
# the pre_install section. This will cause Flipper and
# it's dependencies to be built as a static library and all other pods to
# be dynamic.
#
# NOTE Doing this may lead to a broken build if any of these are also
# transitive dependencies of other dependencies and are expected
# to be built as frameworks.
#
# $static_framework = ['FlipperKit', 'Flipper', 'Flipper-Folly',
# 'CocoaAsyncSocket', 'ComponentKit', 'Flipper-DoubleConversion',
# 'Flipper-Glog', 'Flipper-PeerTalk', 'Flipper-RSocket', 'Yoga', 'YogaKit',
# 'CocoaLibEvent', 'OpenSSL-Universal', 'boost-for-react-native', 'Flipper-Fmt']
#
# pre_install do |installer|
# Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {}
# installer.pod_targets.each do |pod|
# if $static_framework.include?(pod.name)
# def pod.build_type;
# Pod::BuildType.static_library
# end
# end
# end
# end
end

For pure Objective-C projects

For pure Objective-C projects, add the following to your settings:

  1. /usr/lib/swift as the first entry of the LD_RUNPATH_SEARCH_PATHS

  2. Add the following in LIBRARY_SEARCH_PATHS

    "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
    "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
  3. If, after following the above two steps, there are still errors such as Undefined symbol _swift_getFunctionReplacement, then set DEAD_CODE_STRIPPING to YES. The reference for this fix is in the Swift forum

This is done to overcome a bug with Xcode 11 which fails to compile Swift code when bitcode is enabled. Flipper transitively depends on YogaKit, which is written in Swift. For more information about this issue, refer to the Swift code tweet and Github issue.

Install the dependencies by running pod install. You can now import and initialize Flipper in yourAppDelegate.

#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application
withDescriptorMapper: layoutDescriptorMapper]];

[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
...
}
@end

Enabling plugins

Finally, you need to add plugins to your Flipper client. The Layout Inspector plugin is shown above to get you started. See Network Plugin and Layout Inspector Plugin for information on how to add them and enable Litho or ComponentKit support. You can check the sample apps in the GitHub repo for examples of integrating other plugins.

Issues or questions

If you encounter any issues or have any questions, refer to the Troubleshooting section.

- + \ No newline at end of file diff --git a/docs/getting-started/javascript/index.html b/docs/getting-started/javascript/index.html index 029c7974fa5..cc4a972c66e 100644 --- a/docs/getting-started/javascript/index.html +++ b/docs/getting-started/javascript/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Set up your JavaScript App

To set up Flipper in your JavaScript app, you need to add the necessary dependencies, initialize the Flipper client and enable the plugins you want to use. Currently, plugins are not available for JavaScript environments, but you can create your own.

Dependencies

Flipper JavaScript SDK is distributed via NPM. To add it to your app, use either the following:

npm install js-flipper

or

yarn add js-flipper

Application Setup

Flipper SDK works in browser and Node.js environments:

  • browsers - works out-of-the-box if your browser supports WebSockets.
  • node.js - requires a compatible WebSocket implementation (such as ws).
caution

You MUST NOT start Flipper client in production. In browser environments, you should think about not including it in the final production build at all.

To setup Flipper in your browser, use the following:

import flipperClient from 'js-flipper';

// Start the client and pass your app name
flipperClient.start('My cool browser app');

Following is how you can do it in your Node.js app:

import flipperClient from 'js-flipper';
// Say, you decided to go with 'ws' as your WebSocket implementation
// https://github.com/websockets/ws
import WebSocket from 'ws';

// Start the client and pass your app name
// You might ask yourself why there is the second argument `{ origin: 'localhost:' }`
// Flipper Desktop verifies the `Origin` header for every WS connection. You need to set it to one of the whitelisted values (see `VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES`).
flipperClient.start('My cool nodejs app', { websocketFactory: url => new WebSocket(url, {origin: 'localhost:'}) });

flipperClient accepts an options object as a second parameter to its start method. The following code shows what you can pass to it:

interface FlipperClientOptions {
// Make the client connect to a different URL
urlBase?: string;
// Override WebSocket implementation (Node.js folks, it is for you!)
websocketFactory?: (url: string) => FlipperWebSocket;
// Override how errors are handled (it is simple `console.error` by default)
onError?: (e: unknown) => void;
// Timeout after which client tries to reconnect to Flipper
reconnectTimeout?: number;
}

Enabling plugins

Flipper is just a communication channel between the desktop app and your application; its true power comes from its plugins.

All plugins must be added to the client. Client communicates the list of available plugins to the desktop upon connection.

You can add a plugin by calling the following:

flipperClient.addPlugin(/* your plugin */)

Refer to the documentation on creating plugins to write your own!

- + \ No newline at end of file diff --git a/docs/getting-started/react-native-android/index.html b/docs/getting-started/react-native-android/index.html index a14aef1abe5..f0667feabfc 100644 --- a/docs/getting-started/react-native-android/index.html +++ b/docs/getting-started/react-native-android/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

React Native - Manual Android Setup

note

The information within this page is meant for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the React Native Upgrade tool.

Dependencies

Flipper is distributed via Maven Central: add the dependencies to your build.gradle file.

You should also explicitly depend on SoLoader` instead of relying on transitive dependency resolution, which is getting deprecated with Gradle 5.

repositories {
mavenCentral()
}

dependencies {
debugImplementation('com.facebook.flipper:flipper:0.35.0') {
exclude group:'com.facebook.fbjni'
}

debugImplementation('com.facebook.flipper:flipper-network-plugin:0.35.0') {
exclude group:'com.facebook.flipper'
}
}

These exclusions are currently necessary to avoid some clashes with FBJNI shared libraries.

Application Setup

For maximum flexibility, it's recommended you move the Flipper initialization to a separate class that lives in a debug/ folder, so that Flipper code never gets referenced in a release build.

import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import okhttp3.OkHttpClient;

public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);

client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
}
}
}

Note that this only enables the Layout Inspector plugin. For details of more plugins, see the React Native template.

In your Application implementation, call the static method using reflection. This gives us a lot of flexibility, but can be quite noisy. Alternatively, recreate an empty ReactNativeFlipper class in a release/ folder, so you can call into the method directly.

public class MainApplication extends Application implements ReactApplication {
// ...

@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}

/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes
Flipper, since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.example.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}

Further Steps

To create your own plugins and integrate with Flipper using JavaScript, take a look at the Building a React Native Plugin tutorial!

- + \ No newline at end of file diff --git a/docs/getting-started/react-native-ios/index.html b/docs/getting-started/react-native-ios/index.html index 9e984684af6..7c3cb4828d3 100644 --- a/docs/getting-started/react-native-ios/index.html +++ b/docs/getting-started/react-native-ios/index.html @@ -17,7 +17,7 @@ - + @@ -26,8 +26,8 @@

React Native - Manual iOS Setup

note

These details within this page are for people manually adding Flipper to a React Native 0.62+ app. This should only be necessary if you have an existing app that cannot be upgraded with the React Native Upgrade tool.

Dependencies

React Native 0.63+

If using React Native 0.63 or later, your ios/Podfile should look like this:

platform :ios, '10.0'

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

target 'your-app-name' do
config = use_native_modules!
use_react_native!(path: config['reactNativePath'])

# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
use_flipper!({'Flipper' => '0.58.0'}) # should match the version of your Flipper client app
post_install do |installer|
flipper_post_install(installer)
end
end

Install the dependencies by running cd ios && pod install then continue to Initialization.

React Native 0.62

In version 0.62, the setup includes a bit more code (which was moved to a helper in 0.63). -Add all of the code below to your ios/Podfile:

platform :ios, '9.0'

def flipper_pods()
flipperkit_version = '0.259.0' # should match the version of your Flipper client app
pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitReactPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
end

# Post Install processing for Flipper
def flipper_post_install(installer)
file_name = Dir.glob("*.xcodeproj")[0]
app_project = Xcodeproj::Project.open(file_name)
app_project.native_targets.each do |target|
target.build_configurations.each do |config|
cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
cflags << '-DFB_SONARKIT_ENABLED=1'
end
config.build_settings['OTHER_CFLAGS'] = cflags
end
app_project.save
end
installer.pods_project.save
end

target 'your-app-name' do
...
# Replace the existing yoga import with the following (adding modular_headers):
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
...
use_native_modules!

# For enabling Flipper.
# Note that if you use_framework!, flipper will not work.
# Disable these lines if you are doing use_framework!
flipper_pods()
post_install do |installer|
flipper_post_install(installer)
end
end

Install the dependencies by running cd ios && pod install. You can now import and initialize Flipper in your AppDelegate.

Initialization

The code below enables the following integrations:

  • Layout Inspector
  • Network
  • Shared Preferences
  • Crash Reporter

React Native 0.68+

If using React Native 0.68 or later, your AppDelegate should include

...
#import <React/RCTAppSetupUtils.h>

RCTAppSetupUtils takes care of initializing Flipper and the integrations mentioned above.

React Native 0.67

...
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutPlugin/SKDescriptorMapper.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#endif
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self initializeFlipper:application];
...
}

- (void) initializeFlipper:(UIApplication *)application {
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application withDescriptorMapper: layoutDescriptorMapper]];
[client addPlugin: [[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin: [FlipperKitReactPlugin new]];
[client addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
#endif
#endif
}

@end

Lastly, open the Flipper desktop app, and run yarn ios in your terminal.

Issues or questions

If you encounter any issues or have any questions, refer to the Troubleshooting section.

Further Steps

To create your own plugins and integrate with Flipper using JavaScript, check out our Building a React Native Plugin tutorial!

- +Add all of the code below to your ios/Podfile:

platform :ios, '9.0'

def flipper_pods()
flipperkit_version = '0.260.0' # should match the version of your Flipper client app
pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
pod 'FlipperKit/FlipperKitReactPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
end

# Post Install processing for Flipper
def flipper_post_install(installer)
file_name = Dir.glob("*.xcodeproj")[0]
app_project = Xcodeproj::Project.open(file_name)
app_project.native_targets.each do |target|
target.build_configurations.each do |config|
cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
cflags << '-DFB_SONARKIT_ENABLED=1'
end
config.build_settings['OTHER_CFLAGS'] = cflags
end
app_project.save
end
installer.pods_project.save
end

target 'your-app-name' do
...
# Replace the existing yoga import with the following (adding modular_headers):
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
...
use_native_modules!

# For enabling Flipper.
# Note that if you use_framework!, flipper will not work.
# Disable these lines if you are doing use_framework!
flipper_pods()
post_install do |installer|
flipper_post_install(installer)
end
end

Install the dependencies by running cd ios && pod install. You can now import and initialize Flipper in your AppDelegate.

Initialization

The code below enables the following integrations:

  • Layout Inspector
  • Network
  • Shared Preferences
  • Crash Reporter

React Native 0.68+

If using React Native 0.68 or later, your AppDelegate should include

...
#import <React/RCTAppSetupUtils.h>

RCTAppSetupUtils takes care of initializing Flipper and the integrations mentioned above.

React Native 0.67

...
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutPlugin/SKDescriptorMapper.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#endif
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self initializeFlipper:application];
...
}

- (void) initializeFlipper:(UIApplication *)application {
#if DEBUG
#ifdef FB_SONARKIT_ENABLED
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application withDescriptorMapper: layoutDescriptorMapper]];
[client addPlugin: [[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin: [FlipperKitReactPlugin new]];
[client addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
#endif
#endif
}

@end

Lastly, open the Flipper desktop app, and run yarn ios in your terminal.

Issues or questions

If you encounter any issues or have any questions, refer to the Troubleshooting section.

Further Steps

To create your own plugins and integrate with Flipper using JavaScript, check out our Building a React Native Plugin tutorial!

+ \ No newline at end of file diff --git a/docs/getting-started/react-native/index.html b/docs/getting-started/react-native/index.html index 0366c588281..2710ee6f949 100644 --- a/docs/getting-started/react-native/index.html +++ b/docs/getting-started/react-native/index.html @@ -17,15 +17,15 @@ - +
-

React Native App - Automatic Setup

Starting with React Native 0.62, after generating your project with react-native init, the Flipper integration is ready out of the box for debug builds:

  • Android - start the Flipper Desktop application and start your project using yarn android. Your application will appear in Flipper.
  • iOS - run pod install once in the ios directory of your project. After that, run yarn ios and start Flipper. Your application will show up in Flipper.

By default, the following plugins will be available:

  • Layout Inspector
  • Network
  • Databases
  • Images
  • Shared Preferences
  • Crash Reporter
  • React DevTools
  • Metro Logs

Additional plugins can be installed through the Plugin Manager.

To create your own plugins and integrate with Flipper using JavaScript, see the Building a React Native Plugin tutorial.

Using the latest Flipper SDK

By default, React Native might ship with an outdated Flipper SDK. To make sure you are using the latest version, determine the latest released version of Flipper by running npm info flipper.

Latest version of Flipper requires react-native 0.69+! If you use react-native < 0.69.0, please, downgrade react-native-flipper to 0.162.0 (see this GitHub issue for details).

Android:

  1. Bump the FLIPPER_VERSION variable in android/gradle.properties, for example: FLIPPER_VERSION=0.259.0.
  2. Run ./gradlew clean in the android directory.

iOS:

react-native version => 0.69.0

  1. Call FlipperConfiguration.enabled with a specific version in ios/Podfile, for example: :flipper_configuration => FlipperConfiguration.enabled(["Debug"], { 'Flipper' => '0.190.0' }),.
  2. Run pod install --repo-update in the ios directory.

react-native version < 0.69.0

  1. Call use_flipper with a specific version in ios/Podfile, for example: use_flipper!({ 'Flipper' => '0.259.0' }).
  2. Run pod install --repo-update in the ios directory.

Manual Setup

If you are not using a default React Native template or cannot use the upgrade tool, you can find instructions for how to integrate Flipper into your projects in the following guides:

- +

React Native App - Automatic Setup

Starting with React Native 0.62, after generating your project with react-native init, the Flipper integration is ready out of the box for debug builds:

  • Android - start the Flipper Desktop application and start your project using yarn android. Your application will appear in Flipper.
  • iOS - run pod install once in the ios directory of your project. After that, run yarn ios and start Flipper. Your application will show up in Flipper.

By default, the following plugins will be available:

  • Layout Inspector
  • Network
  • Databases
  • Images
  • Shared Preferences
  • Crash Reporter
  • React DevTools
  • Metro Logs

Additional plugins can be installed through the Plugin Manager.

To create your own plugins and integrate with Flipper using JavaScript, see the Building a React Native Plugin tutorial.

Using the latest Flipper SDK

By default, React Native might ship with an outdated Flipper SDK. To make sure you are using the latest version, determine the latest released version of Flipper by running npm info flipper.

Latest version of Flipper requires react-native 0.69+! If you use react-native < 0.69.0, please, downgrade react-native-flipper to 0.162.0 (see this GitHub issue for details).

Android:

  1. Bump the FLIPPER_VERSION variable in android/gradle.properties, for example: FLIPPER_VERSION=0.260.0.
  2. Run ./gradlew clean in the android directory.

iOS:

react-native version => 0.69.0

  1. Call FlipperConfiguration.enabled with a specific version in ios/Podfile, for example: :flipper_configuration => FlipperConfiguration.enabled(["Debug"], { 'Flipper' => '0.190.0' }),.
  2. Run pod install --repo-update in the ios directory.

react-native version < 0.69.0

  1. Call use_flipper with a specific version in ios/Podfile, for example: use_flipper!({ 'Flipper' => '0.260.0' }).
  2. Run pod install --repo-update in the ios directory.

Manual Setup

If you are not using a default React Native template or cannot use the upgrade tool, you can find instructions for how to integrate Flipper into your projects in the following guides:

+ \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/android/index.html b/docs/getting-started/troubleshooting/android/index.html index c27c0d97384..4d4ecb69542 100644 --- a/docs/getting-started/troubleshooting/android/index.html +++ b/docs/getting-started/troubleshooting/android/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Troubleshooting Android Issues

Flipper is a 'work in progress' and issues may occur. This page contains known issues associated with the Android platform and provides steps you can take to try to resolve them.

Stuck in "Currently Connecting..."

There are sadly many non-deterministic reasons why Flipper may not be able to connect. It can often help to restart all pieces related to the connection:

  • Kill the app on the emulator and restart it.
  • Restart the emulator.
  • Restart adb with adb kill-server && adb start-server.
  • The nuclear option: Restart your computer (especially when USB connectivity is involved)

In-app diagnostics

The Flipper SDK includes an in-app connection diagnostics screen to help you diagnose problems.

Replace <APP_PACKAGE> below with the package name of your app, for example, such as com.facebook.flipper.sample.

On a terminal, run the following:

adb shell am start -n <APP_PACKAGE>/com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity

This will only work if you added FlipperDiagnosticActivity to your AndroidManifest.xml. See getting started for help.

Exception from call site #4 bootstrap method

Build error after including the Flipper dependency:

Exception from call site #4 bootstrap method

This can happen because we include OkHttp3 as dependency which makes use of Java 8 features. There are two ways of dealing with this:

Enable Java 8 support

Add this to your Gradle config:

android {
compileOptions {
targetCompatibility = "8"
sourceCompatibility = "8"
}
}

Exclude the OkHttp3 dependency

Alternatively, if you don't plan on making use of OkHttp, you can exclude the dependency from the build entirely:

debugImplementation('com.facebook.flipper:flipper:*') {
exclude group: 'com.squareup.okhttp3'
}

Duplicate class com.facebook.jni.*

This can occur when mixing different versions of FBJNI, a library we use to interact with native C++ code.

Speficially, this can happen when the versions 0.0.x and 0.1.x are mixed. Version 0.1.0 of FBJNI switched to using Google Prefab for distributing native artifacts, which made the split into combined, "java-only" and "header" packages redundant and only requires a single dependency in your projects.

When including both "fbjni-java-only:0.0.1" and "fbjni:0.1.0" in one project, you will now duplicate class errors during the build process. You must ensure that only one of the two versions is used in your entire dependency tree. Start by looking at ./gradlew :myapp:dependencies to see where the different version requirements come from. Then exclude the FBJNI dependency from one of them, as follows:

implementation("com.facebook.react:react-native:+") {
exclude group:'com.facebook.fbjni'
}
- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/general/index.html b/docs/getting-started/troubleshooting/general/index.html index 2d3a62a3ca4..70245d049b8 100644 --- a/docs/getting-started/troubleshooting/general/index.html +++ b/docs/getting-started/troubleshooting/general/index.html @@ -17,7 +17,7 @@ - + @@ -29,7 +29,7 @@ it is possible to delete them and there is no easy way to restore them afterwards. You can use a script like this one to recreate the default set.

- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/index.html b/docs/getting-started/troubleshooting/index.html index e3c8882224e..9f3f8e38046 100644 --- a/docs/getting-started/troubleshooting/index.html +++ b/docs/getting-started/troubleshooting/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Troubleshooting Introduction

Flipper is a 'work in progress' and, as such, there's a chance that issues may occur while you're using it.

This 'troubleshooting' section contains known issues that have occurred within various Flipper environments and the steps you can take if you encounter them.

How to file an issue or ask a question?

If you are still blocked after checking Troubleshooting guide you may file an issue on GitHub with the chrome DevTools logs and the output from the diagnostics screen, if relevant.

- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/install-android-sdk/index.html b/docs/getting-started/troubleshooting/install-android-sdk/index.html index 5bdd8ac01dc..85583367709 100644 --- a/docs/getting-started/troubleshooting/install-android-sdk/index.html +++ b/docs/getting-started/troubleshooting/install-android-sdk/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Install Android SDK

Make sure Android Studio is installed.

  • Launch Android Studio

    • If it is reported as "damaged", open Applications directory in Finder, right click Android Studio.app and select "Open"
  • Do not send statistics to google

  • "Next" to download SDK
  • "Next" to download selected SDK
  • "Next" to verify settings
  • Accept License Agreement and click "Finish"
  • Wait for install to finish (about 2 minutes)
  • "Finish" for Downloading Components
- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/install-ios-sdk/index.html b/docs/getting-started/troubleshooting/install-ios-sdk/index.html index 781ca3ed04c..e632c0ae490 100644 --- a/docs/getting-started/troubleshooting/install-ios-sdk/index.html +++ b/docs/getting-started/troubleshooting/install-ios-sdk/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Install iOS SDK

Make sure Xcode is installed. Launch Xcode.

  • Launch Xcode
  • Accept SDKs Agreement
  • Select iOS platform and optionally other
  • Click Download & Install
Don't see the "sdk install prompt"?
  • Select "Xcode" > "Settings"
  • Make sure that iOS is installed. Click "Get" if it is not installed.
  • Wait for SDK's to install
  • Dismiss What's new in Xcode
- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/ios/index.html b/docs/getting-started/troubleshooting/ios/index.html index 65e0b754116..e0c87bed6a9 100644 --- a/docs/getting-started/troubleshooting/ios/index.html +++ b/docs/getting-started/troubleshooting/ios/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Troubleshooting iOS Issues

Flipper is a 'work in progress' and issues may occur. This page contains known issues associated with the iOS platform and provides steps you can take to try to resolve them.

In-app diagnostics

You'll need to manually add this ViewController to your app to see the in-app diagnostics.

iOS device not showing up

Make sure idb is installed and configured in the Flipper settings.

iOS simulator device not showing up

Ensure that your simulator is on the same version as selected in xcode-select. You can do that by checking that commands ps aux | grep CoreSimulator and xcode-select -p shows the same Xcode version. If not, update the xcode version by sudo xcode-select --switch <Path to xcode>

iOS app connection error "Connection failed. Failed to find device..."

If during connecting iOS app to Flipper you see error message "Connection failed. Failed to find device [device_id] while trying to connect app" - try executing idb kill on a terminal and restarting Flipper as workaround to reset idb state.

- + \ No newline at end of file diff --git a/docs/getting-started/troubleshooting/react-native/index.html b/docs/getting-started/troubleshooting/react-native/index.html index 83b0c6bc5f7..4c7eb863b15 100644 --- a/docs/getting-started/troubleshooting/react-native/index.html +++ b/docs/getting-started/troubleshooting/react-native/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Troubleshooting React Native Issues

Flipper is a 'work in progress' and issues may occur. This page contains known issues associated with React Native and provides steps you can take to try to resolve them.

Make sure the project is using the latest Flipper SDK.

When using Flipper with React Native, two devices should show up:

  1. The 'React Native' device - this is the "device" that talks to the Metro server and shows plugins that connect to or through Metro, such as the React DevTools, the Hermes Debugger and Metro logs.
  2. The device on which your app is running - This is the device that Flipper connects to ADB / IDB. Flipper primarily support emulators and USB connected devices (examples: SM760FS, Pixel 3, android_emulator, and iPhone 11).

I don't see the 'React Native' device

  1. Make sure that the Metro server is running.
  2. Make sure you are on React Native 0.62 or higher.
  3. Verify that the @react-native-community/cli version is 0.47.0 or higher (using yarn why @react-native-community/cli).

I see my device / emulator, but I can't see the app

  1. Make sure you are running a debug build. Flipper only supports debug builds.
  2. If you are upgraded from RN < 0.62.2, make sure you've updated all the dependencies and build related files according to the upgrade helper. For example: https://react-native-community.github.io/upgrade-helper/?from=0.61.4&to=0.62.2
  3. If you upgraded, make sure you made a clean build: cd android && ./gradlew clean, cd iOS && pod install --repo-update.
  4. For iOS, make sure it works on a simulator first.
  5. (Unconfirmed) check the deployment info target in the XCode project settings. Target should be iOS 9.0.

I'm seeing "certificate verify failed, type = SSL error" in the logs

Ensure that your device is set to the correct time and time zone for TLS verification to work.

I see my app, but I don't see the external plugins I've installed

  1. Make sure you've installed the desktop part of the plugin (usually through 'Manage plugins' in Flipper). If no plugins show up under 'Manage plugins' > 'Status' make sure you've selected your running device in the Flipper toolbar (and not 'React Native').
  2. Make sure you've installed the app part of the plugin. Typically, the installation instructions of the plugin itself need to be followed here.
  3. Make sure you've installed the latest version of react-native-flipper in your app, and run pod install in the iOS dir afterwards.
  4. Make sure you've restarted both Flipper and your app.

On iOS it seems that the Flipper dependencies are compiled when making a release build

That is correct. The dependencies won't be included in the release (when using react-native-flipper > 0.45) and can't be excluded from the build process.

The React DevTools don't seem to connect

  1. Make sure there are no other instances of the React DevTools are running (such as a stand-alone version). Restart Flipper if needed after closing other tools.
  2. Make sure you have only one device running to connect to. If there are multiple devices, close them and try again (restart Flipper if needed).
  3. Make sure there is only one RN app running on the device.

Cannot inspect an element in the React DevTools: "Could not inspect element with id ..."

On selecting a specific element in the React DevTools, the "Could not inspect element with id XXX" appears when the version of the DevTools shipped in Flipper is incompatible with the react-devtools-core package used by the React Native application.

Flipper supports using a globally installed react-devtools (after using npm install -g react-devtools@x.x.x) instead of the embedded one. This should help with any compatibility issues.

Another way to fix this is to set the resolutions field in the package.json of the app to force a specific version and then run yarn install, for example:

"resolutions": {
"react-devtools-core": "4.13.2"
}

How to select a specific element in the React DevTools?

  1. Trigger the debug menu on your RN app (you can use the button in the Flipper toolbar).
  2. Use Show Inspector. Flipper now follows the selection on your device.

The Hermes Debugger does not connect

  1. Make sure the Hermes engine is enabled.
  2. Make sure only one device with React Native is running.
  3. Make sure there is only one RN app running on the device.
  4. Make sure the React Native app is not in debug mode already (trigger dev menu, and make sure that Remote JS Debugging is not enabled).

iOS Build errors in React Native

First, make sure your cocoapods is up to date (sudo gem install cocoapods), and that you are using the latest FlipperKit.

For inexplainable build errors, clone and verify if our reference project builds and runs locally. If it does, it's recommended to compare the package.json and ios/Podfile files with yours. If that doesn't yield anything, compare the ios/Podfile.lock as well to verify any transitive pod dependencies need updating.

YogaKit.modulemap not found

  1. Make sure you are opening the .xcodeworkspace dir when building from XCode, not the project file. Pods based projects should always be opened this way.
  2. Make sure you've run cd ios && pod install.
  3. Restarting your machine seems to magically fix the issue for quite some people. This might especially be needed after doing an XCode upgrade.
  4. Make sure that the simulators are spawned by your current Xcode version. Force quite all simulators, run sudo xcode-select --switch /Applications/Xcode.app (update path were necessary) and start simulators & Flipper again.
  5. Make sure the iOS build target version aligns with the podfile and target iOS 11 (see the following Example).
  6. Verify XCode has enough permissions.
  7. More solutions might be found in this thread.

Swift errors

If you experience errors such as Undefined symbol: associated type descriptor for FloatLiteralType or Undefined symbol: __swift_FORCE_LOAD_$_swiftCompatibility50 after going through the Getting Started page, do the following:

  1. Open your project in Xcode.

  2. Click in your project's name on the left side.

  3. Make sure that you choose your project under PROJECT on the screen that has been opened.

  4. Go to the tab Build Settings.

  5. Search for LD_RUNPATH_SEARCH_PATHS (make sure that All is selected).

  6. Double-click Runpath Search Paths and, in the dialog that opens, click on the plus button at the bottom-left corner and paste /usr/lib/swift $(inherited) there.

    Screenshot showing the places mentioned in the first steps
  7. Now search for LIBRARY_SEARCH_PATHS.

  8. Double-click Library Search Paths and, in the dialog that opens, add the following items.

    "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
    "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"
    "$(inherited)"
    note

    All of them should be added as non-recursive (the default).

    In the end it should look as follows:

    Screenshot showing the places mentioned in the last steps

Now you can run your build normally.

Opting out from Flipper (iOS)

Comment out the relevant lines in ios/Podfile and run cd ios && pod install again:

  # use_flipper!
# post_install do |installer|
# flipper_post_install(installer)
# end

Disable Flipper on CI builds (iOS)

To speed up CI builds, Flipper can be disabled on CI environments by making the Flipper SDK inclusion conditional (this works on most CI providers, feel free to customize the environment variable):

  if !ENV['CI']
use_flipper!
post_install do |installer|
flipper_post_install(installer)
end
end
- + \ No newline at end of file diff --git a/docs/internals/contributing/index.html b/docs/internals/contributing/index.html index 67dd5120f0e..5b3338d7bf2 100644 --- a/docs/internals/contributing/index.html +++ b/docs/internals/contributing/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Contributing to the Codebase

This page contains information that will help you make contributions to the Flipper codebase.

Changelog Entries

A changelog dialog is displayed when a new version of Flipper is started for the first time. It can also be seen by clicking on the button next to the version number in the 'Welcome' screen.

The changelog dialog in Flipper

The content is automatically generated during the release process. You should always include a changelog entry if there is a user-visible change in your commit.

To contribute a message, include a note in the body of your commit like in example below.

note

Unlike other blocks that Phabricator expects (such as 'Summary' and 'Test Plan'), the changelog can only be a single line. The spelling is not case-sensitive.

[layout] Add Unicorn support

Summary:
Finally, the moment we've all been waiting for: Unicorn support!

Changelog: Unicorns can be inspected in the Layout Plugin

Test Plan:
...

Implementation

You can find the code for the changelog generation in desktop/scripts/generate-changelog.js.

- + \ No newline at end of file diff --git a/docs/internals/device-identifiers/index.html b/docs/internals/device-identifiers/index.html index 2ee9baab312..e13fefad070 100644 --- a/docs/internals/device-identifiers/index.html +++ b/docs/internals/device-identifiers/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Device Identifiers

Flipper is capable of discovering and listing available devices for multiple platforms. It enables users to check for connected devices and quickly access a number of plugins:

  • Logs
  • Mobile Builds
  • Crash Reporter

Device information, including identifiers, is obtained using either idb list-targets --json on iOS or adb devices on Android.

On Flipper Desktop, apps are shown alongside the device it's running on.

Unfortunately, on the client side, obtaining the device identifier is a problem.

How does Flipper map connecting apps to their respective devices?

Over the years, for security reasons, both Apple and Google have locked down their APIs so that apps can't find any value that could be considered unique such as a MAC address or serial numbers. This means there's no OS API available.

To obtain this device identifier, a two-way approached had to be used.

Obtaining the device identifier

From the Flipper Desktop side, connected devices are discovered and listed using idb or adb as mentioned above, by which it sees some TCP connections coming in from apps. It is during the certificate exchange process that we do the mapping from apps to device.

In this process, an app creates and sends a Certificate Signing Request (CSR) to Flipper Desktop alongside some other data including the path in the application sandbox where this CSR was originally written.

When Flipper Desktop is processing the request, it effectively lists all possible connected devices and proceeds to try and pull the CSR from each device. The CSR is then compared with the one sent by the client. If there's a match, we have successfully made the pairing and the device identifier is sent back to the client.

The client receives the device identifier and uses this information for subsequent secure TCP connections.

When a secure TCP connection is established, the device identifier is passed along and thus Flipper is able to map the connecting app to its running device.

The following diagram depicts this process.

Device Identifier Sequence

- + \ No newline at end of file diff --git a/docs/internals/documentation-formatting/index.html b/docs/internals/documentation-formatting/index.html index 33518d1e83e..2ee33f7b040 100644 --- a/docs/internals/documentation-formatting/index.html +++ b/docs/internals/documentation-formatting/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Markdown Formatting

This page provides recommendations on the use of a variety of Markdown features that help you create properly formatted documentation.

For tips and guidelines on when to use the same tools, and several others, see the Writing Guide.

Structural format

Headers

  • Start each main section with a level 2 header.
  • Sub-sections should follow a hierarchical structure and should use header levels 3 to 5.

Markdown Example:

The following example Markdown shows how to use headers.

## Level 2 header

### Level 3 header

#### Level 4 header

##### Level 5 header

Markdown tools

Backticks

Use Markdown backticks ( ` ), to provide emphasis for items such as file names, classes, methods, parameters, and expressions.

Let's use the `TestComponent`, which has one direct child, `InnerComponent`, and one non-direct child, `Text`.

Result: Let's use the TestComponent, which has one direct child, InnerComponent, and one non-direct child, Text.

Code Snippets

For code snippets, remember to add the language tag (javascript is used in the following example).

```javascript
import {addPlugin} from "react-native-flipper"

addPlugin({
getId() {
return 'ReactNativeExamplePlugin';
},
onConnect(connection) {
mammmals.forEach(({ title, pictureUrl }, index) => {
connection.send('newRow', {
id: index,
title,
url: pictureUrl
})
})
},
onDisconnect() {
}
})

Result:

import {addPlugin} from "react-native-flipper"

addPlugin({
getId() {
return 'ReactNativeExamplePlugin';
},
onConnect(connection) {
mammmals.forEach(({ title, pictureUrl }, index) => {
connection.send('newRow', {
id: index,
title,
url: pictureUrl
})
})
},
onDisconnect() {
}
})

For more code blocks features, such as line highlighting, see the Docusaurus Code blocks document.

Avoid using bare URLs in your documentation. Instead, use referenced hyperlinks, as shown in the following table.

TypeCodeDisplays as
Bare URLUpload the video to Pixelcloud at https://www.internalfb.com/intern/px/searchUpload the video to Pixelcloud at https://www.internalfb.com/intern/px/search
ReferencedUpload the video to [Pixelcloud](https://www.internalfb.com/intern/px/search)Upload the video to Pixelcloud
tip

Notice the use of square brackets around 'PixelCloud' in the referenced hyperlink.

Tabs

To organize content in tabs, Docusaurus provides the Tabs React component:

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<Tabs
groupId="platform"
defaultValue="kotlin"
values={[
{label: 'Kotlin', value: 'kotlin'},
{label: 'Java', value: 'java'},
]}>
<TabItem value="kotlin">
Information about using Kotlin with Flipper.
</TabItem>
<TabItem value="java">
Information about using Java with Flipper.
</TabItem>
</Tabs>

Result:

Information about using Kotlin with Flipper.
tip

To sync several Tabs components on the website set the same groupId for them. More info in Docusaurus Tabs Syncing docs.

Resources

For additional information, see the following resources:

- + \ No newline at end of file diff --git a/docs/internals/documentation-standards/index.html b/docs/internals/documentation-standards/index.html index fb1f5fb961e..71383b56e2c 100644 --- a/docs/internals/documentation-standards/index.html +++ b/docs/internals/documentation-standards/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Flipper Docs Standards

Key standards

The Flipper Docs team is committed to writing docs that serve users. To measure and maintain satisfactory documentation quality, the following key standards are used:

CompletenessDiscoverabilityAccessibilityAccuracy
Is the information sufficient to give the reader a good understanding or to provide a solid solution to a problem?If the information exists, is it easy to find?Is the information accessible to readers of different capability?Is the information accurate and up to date?

To help you achieve each of these standards, several points for consideration are listed in the following sub-sections (or you can select a column header to link to its sub-section).

Completeness

Completeness is determined by how many questions, and the type of questions, the reader may have after reading your document. For example, if after reading a Flipper document on 'getting help and providing feedback', the reader asked the question "Where do I go if I'm a noob?" then the document might not be complete.

To increase your document's completeness, consider the following:

  • Limit Assumptions.
  • Trade-offs and limitations of APIs and approaches should be discussed openly.
  • Where possible, describe alternatives to what you're describing: understanding the available options empowers people to feel confident in their choices.
  • Give tips you think are particularly useful.
  • The user shouldn't have to navigate to different documentation sources (such as Wiki pages and posts) to gain a core understanding.
  • Explain what success looks like.
  • The discussion of a technical topic shouldn't be in the 'Getting Started' section.
  • Is each step explained, or does it rely on other knowledge?
  • Empathize with the reader. Consider questions such as: "Are they trying to learn how to do something?" and "Has something gone wrong and they're trying to figure out if a solution is possible?"

Discoverability

Discoverability is the degree to which a piece of information (instruction, guideline, code snippet, and so on) can be found within the Flipper Documentation website.

To improve your document's discoverability, consider the following:

  • Add appropriate keywords to your file. The keywords are used by Static Doc's search tool.
  • Is the information easy to find. Test for keyword searches to see if and where in the result list your documentation is listed.
  • Consider the placement of the file (if you're adding a new one).

Accessibility

Accessibility is the measure of how easy it is for users of different capability to read, understand and navigate your documentation.

To improve your document's accessibility, think about the following:

  • Is non-standard terminology fully defined before being used (this includes the use of abbreviations).
  • Is it easy to understand and apply the documented information?
  • The most effective medium (words, diagrams, images, videos, examples, or a combination).

Accuracy

Accuracy is determined by how relevant, correct, and up to date the content of your documentation is.

To ensure your document's accuracy, think about the following:

  • Ensure that all 'Live' code samples are relevant to the topic being covered and provide appropriate output.
  • Explain the relevant aspects of the snippet functionality clearly.
  • When making any changes to procedure, architecture, or code, make the documentation of those changes part of the change request.
- + \ No newline at end of file diff --git a/docs/internals/documentation-writing-guide/index.html b/docs/internals/documentation-writing-guide/index.html index 91ae38eb065..3d4094f7636 100644 --- a/docs/internals/documentation-writing-guide/index.html +++ b/docs/internals/documentation-writing-guide/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Writing Guide

To assist you in writing your documentation, this page contains tips and guidelines on how to create effective documentation that is valued by your target reader (a measure of its success).

Flipper uses StaticDocs, which is based on Docusaurus, for the Flipper documentation website. The StaticDocs homepage is an excellent starting point for information.

Why Documentation Is Needed

Some of the benefits of documentation include:

  • Immediate availability - from the easily accessible Flipper Documentation website
  • Fewer support requests - the more information that's available online, the fewer the number of support requests. Also, colleagues that raise requests can be directed to the documentation website, which frees up the time of the person being asked.
  • Knowledge base - provides a permanent record of Flipper knowledge, rather than it being isolated to one or more specialists.

By making documentation part of your work routine, it becomes less of a burden: the more you write, the easier it gets.

Adding or editing content

You may be creating content for a new document or updating the content of an existing document. Whatever the reason, there are two key points to consider before you make any changes:

  1. Tone of voice - this is the 'voice style' used to communicate the content to the reader.
  2. Documentation style - this is the tools used to communicate the content to the reader, such as bullet points, images, tables, bold text, headers, videos, and so on. The trick is knowing when to use them and how to use them effectively.

Both of these key points are detailed in the following sub-sections.

Tone of voice

Important

The content of any material you create for any aspect of Meta's documentation must fully comply with Meta's values, policies, and initiatives, and must incorporate Meta's principles of diversity, equity and inclusion.

For details, see the following:

Consider how you'd explain a work-related task to a colleague; the words you'd use and the manner in which you'd say them. Following are some points to consider, which will help you to adopt the write 'tone of voice' in your documentation:

  • Semi-formal - imagine talking to a colleague whom you've just met in the workplace. When explaining something to them, you wouldn't want to be too formal, but you'd also like to appear friendly.
  • Professional - all communication must follow Meta company guidelines and policies and incorporate the principles of diversity, equity and inclusion.
  • Descriptive - don't go off-topic: keep to the topic that is relevant to the page/section. Textual information should be well-explained but not excessive. Lists, images and videos can be used to reduce large blocks of text (see Markdown Formatting).
  • Engaging - the correct use of pronouns can aid comprehension and help to keep the reader engaged and comprehension (see below).

The magic of pronouns

The correct use of pronouns can increase the reader's engagement, enjoyment, and comprehension of the information in your documentation. Following are some guidelines on when to use particular pronouns (it does matter):

  • Use the pronouns 'You', 'Your', and 'Yours' - the main benefit of using them is that it increases the reader's sense of involvement in the content and promotes a friendly tone by addressing the reader directly (think about how you'd explain something complex to your colleague). As a result, your page's reader will find the content more engaging, easier to remember, and easier to understand. These pronouns are especially useful when you want to detail a series of steps that the reader must follow.
  • Use the pronouns 'We' and 'Our' - the main reason for their use is that they emphasise the reader's team or organisation, which gives the reader a sense of community and team spirit. These pronouns are especially useful when you want to describe something that involves the Flipper team, such as the reasons for the decision to use a particular technology or how the reader's actions can benefit the team.
  • Don't use the pronouns 'I' and 'My' - the main reason is that it emphasizes the writer rather than the reader. Also, it may cause confusion, reduce engagement, or be the source of annoyance as the reader may not know the identity of the writer.

Spelling and grammar

Bad spelling and grammar can have a negative effect on the tone of voice in your document (making it irritating to read and difficult to understand).

The problem is that we all make unintentional spelling and grammar errors when writing. Fortunately, there are three steps you can take to reduce (or, hopefully, remove all) those errors:

  1. Read it - when you think you've finished, read though the content and make the appropriate corrections.
  2. MS Word it - use the spelling and grammar checker to identify any errors.
  3. Review it - before your page is published, it will be reviewed as part of its Diff lifecycle. Make sure you consider all of the changes suggested by the Reviewers.

This is not going to be 100% effective every time, but it will definitely help to get as close to 100% as possible.

Documentation style

Documentation style involves two areas of interest:

  • Structural Format - this includes the format of the page title and the headers and includes topics such as:
    • How many words to use.
    • How to capitalise.
    • How to improve the discoverability of the content.
  • Documentation Tools - this includes the following:
    • When and how to use the available tools (such as bullet points, tables images, videos, and much more).
    • How to format the content for the selected tool.

Structural format

Page title

The title provides an at-a-glance summary of a page's content. It is also used in the SideBar so assists with navigation of the Flipper Documentation website. Consider the following guidelines before choosing a page title.

  • Short but descriptive - the title should be short but also descriptive enough to convey the main topic of the page's content.
  • Capitalization - use the 'Title' case associated with the Chicago Manual of Style (17th edition). For details of a helpful online tool, see Header and title capitalization tool, below.
  • Titles should be unique - use a title that is not used elsewhere within the Flipper Documentation website.

Headers

Good documentation is split into a series of sections that are logically structured and cover the subject matter. Headings are used for the document's Table of Contents, which provide the reader with an outline of the document and assists with navigation.

  • Headers indicate document structure - start each main section with a level 2 header. Sub-sections (header levels 3 to 5) should follow a hierarchical structure and be associated with, or relevant to, their parent header.
  • Keep headers short - while keeping your headers as short as possible, make sure they contain enough words to indicate the information contained within the section/sub-section.
  • Keep Headers unique - consider how a header will be listed in the result set of a search from the Static Docs website.
  • Capitalisation of headers - use the 'Sentence' case associated with the Chicago Manual of Style (17th edition). For details of a helpful online tool, see Header and title capitalization tool, below.
  • Use Blank Lines - make sure there is one blank line between a heading and the text in its section/sub-section.

Header and title capitalization tool

To assist with the capitalization of your page's title and headers, go to the online tool Capitalize My Title, then:

  • For the title - select the 'Chicago' tab and click the 'Title Case' option.
  • For headers - select the 'Chicago' tab and click the 'Sentence case' option.

As you enter the title/header, the tool automatically converts it to the selected style, which you can then copy/paste into your editor.

Markdown code

For examples of using Markdown for headers, see Markdown Formatting

Markdown tools

Code snippets

Code examples are one of the best ways to help your readers take their understanding to the next level by providing them with something they can actually view and experiment with on their own.

note

Remember that a snippet is 'a small part or piece of a thing' so keep your snippet as short as possible and relevant to the section in which it's located.

When providing code snippets, first create an example in the Flipper shell and then directly reference that example in your documentation. This enables the Flipper website to ensure that code in its documentation always stays up-to-date and functional

Make sure there is one blank line between the code snippet and any surrounding text.

Markdown code

For an example of using Markdown for code snippets, see Markdown Formatting

Images

Images includes pictures, diagrams, and screenshots.

The well-known adage "A picture is worth a thousand words." is true but must be accompanied by knowledge of the best way to use images, and an awareness of their limitations. Following are guidelines for the use of images in your documentation:

  • No sensitive or personal data - if you're taking a screenshot that features data, ensure it is test or sample data.
  • Use a lossless format - PNG and SVG files are ideal for websites and PDFs, other formats may look blurred.
  • Use a transparent background - by using a transparent background, you avoid potential color clashes and unwanted image borders caused by your reader's use of a colored or themed background.
  • Avoid colored borders and shadows - a colored border or a shadow might look great against the color you are using but could look ugly against a differently colored background.
  • Consider dark mode backgrounds - when relevant, test your image using light and dark mode; as a result, you may need to replace black/dark grey colors with pastel-colored alternatives (note: this does not apply to screenshots).
  • Size for readability:
    • There's little purpose in using any type of image if the text within it can't be read or needs a magnifying glass.
    • If your image is large, consider splitting into two or more images and show how they are connected.
    • If using a screenshot, zoom in on a specific area of the screen and provide context rather than capturing the whole screen.
  • Be consistent - use the same color and style for callouts and annotations throughout your documentation.
  • Refer to the image - mention the image within the accompanying text. Such as "As shown in the following image...", or "The image below provides an overview of..."
tip

Keep in mind that images are meant to complement text, not replace it. Though a picture may be worth a thousand words, the reader still needs the detail contained within the text.

Latin abbreviations

The use of some Latin abbreviations (such as 'e.g.', 'etc.', 'i.e.', 'et al.', and so on) should be avoided for the following reasons:

  • Readers of the documentation may not use English as their 'first' language and may be unaware of the meaning of Latin abbreviations.
  • Often, when used, Latin abbreviations are not correctly punctuated, which detracts from the professional tone of the page's content.

When tempted to use the Latin abbreviations shown in the following table, consider using the English equivalent.

LatinEnglish Equivalent
e.g.for example
et al.and colleagues / associates / team members
etc.and so on / and the rest
i.e.that is
N.B.Note
note

Key point: whenever possible, use plain, easy-to-understand English in your documentation.

Within Flipper Docs, links are usually text-based and can be used to navigate your readers to several types of link targets, such as the following 'good' links:

As can be seen in the link examples above, a link consists of the parts shown in the following table (but not necessarily in the same order).

Link partUsing the first example, above
Leading Phrase"For information on the type of links to avoid, see"
Target description[Bad links]
(Optional) Location", below" or "website"
Target(#bad-links) or a URL
Tips
  • Navigation - tell your readers where they are going, especially if the link takes them away from the Flipper Docs website (see Bad links, below).
  • Test - check that your links do reach the expected target.
  • Access - keep in mind that some readers may not have access to certain pages or domains.
  • Backticks - don't use backticks in links as it changes the link's appearance. It has to look like a link for the user to know they can click on it.

It's worth remembering that the reader won't know beforehand where a link is taking them unless it's stated (or at least suggested) in the 'Target description' or the 'Location'.

Therefore, it's not a good idea to use links such as the following:

  • For information on the type of links to avoid, see below.
  • Click here to see examples of using Markdown for headers.
  • To learn all about Litho, click here.

'Bad' links could lead to navigation confusion, frustration, and loss of comprehension.

Markdown code

For additional guidelines on using Markdown for links, see Markdown Formatting.

Lists

If the information you wish to communicate involves a series of steps, follows a defined procedure, or indicates preference or ranking, use a numbered list, which is also known as an 'ordered' list.

If the order of items in the list is irrelevant, use bullet points, which are known collectively as an 'unordered list'.

Bullet Points

  • Be concise but effective - use as few words as possible but make sure the meaning is not lost.
  • Capitalise the first word - do this for each bullet.
  • One sentence per bullet point - try to stick to one sentence per bullet point.
  • Use emphasis - where possible, emphasize the beginning of each bullet point to give the reader the chance to skim through easily but still get a basic understanding of the content of the list.
  • Use sentences or fragments, not both - within a single list, avoid using bullets that are full sentences mixed with other bullets that are just sentence fragments, phrases, or names.
  • Punctuate consistently:
    • If at least one bullet is a sentence, end all bullets with a full stop. Don't end a bullet with a semicolon (;).
    • If all bullets are phrases, or fragments, use no end punctuation.
  • Use Blank Lines - make sure there is one blank line above and below the list.

Numbered List

  1. Be clear - keep each item in the list to a specific topic or instruction.
  2. Be logical - the items in the list should follow a logical flow, which guides the reader though the content in a manner that makes sense.
  3. Keep the structure simple - if your list is getting a bit complex in structure or stretches over several pages, consider breaking it up into a series of sub-sections.
  4. Punctuate consistently - each item in the list should end with a full stop, unless the item contains bullet points.
  5. Use Blank Lines - make sure there is one blank line above and below the numbered list.
Example lists

The bullet points used throughout this page provide examples of the style to be used.

note

Sometimes, it might not be possible to use an emphasized phrase (the part that is in bold) for each bullet. In such cases, try to apply the other bullet point guidelines to your list.

Tabs

Since Flipper supports multiple platforms, you may need to provide information that is specific to each platform. The Tab tool provides an excellent way of this information via use of the CodeLanguageTabs component.

Tabs provide your reader with an easy method of switching from one platform (or process) to another without having to scroll up and down the page.

Markdown code

For an example of using Markdown for tabs, see Markdown Formatting

Videos

Videos are an effective method of presenting a lot of information to the reader. However, just like images, the use of videos must be accompanied by knowledge of how to use them effectively. Following are guidelines for the use of video in your documentation:

  • Watch the video - before you use the video in your documentation, watch it to check that its good quality, relevant to the section in which it's located, and provides sufficient information.
  • Keep the content concise:
    • The content of the video should be limited to the specific purpose for which it is being used.
    • For example, if a video is being used to illustrate configuration of a component, it should contain just that process.
  • Refer to the video - mention the video within the section in which it's being used.
  • State the length of the video:
    • If the length of the video is not shown in the video object, state it in the text.
    • For example, "The following 11-second video demonstrates the slightly different 'Hg: Show Head Changes' command".
tip

When deciding to use a video, keep in mind that it takes much more time to produce a video than to change some text. A small change to a UI or a process is relatively easy to change in text. The same change in a video may mean it needs to be replaced or removed, which, ultimately, involves much more work.

Resources

For additional information, see the following resources:

- + \ No newline at end of file diff --git a/docs/internals/index.html b/docs/internals/index.html index f77b9bdd4d3..67f212694a3 100644 --- a/docs/internals/index.html +++ b/docs/internals/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Introduction

This part of the site is for those interested in how Flipper works 'under the hood'.

'Under the Hood' contains the following topics:

- + \ No newline at end of file diff --git a/docs/internals/linters/index.html b/docs/internals/linters/index.html index 3e646bc6711..71c4ef59563 100644 --- a/docs/internals/linters/index.html +++ b/docs/internals/linters/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Linters

Flipper Desktop comes with a variety of ESLint checks pre-enabled, which enable us to enforce sustainable coding practices and skip over discussions in code reviews.

Specific Linters

This section contains an incomplete list of unusual linters we deploy, why we use them, and how to fix them (where relevant).

promise/no-nesting

  • Summary - avoid nested then() or catch() statements. For more details, see no-nesting.md on GitHub.
  • Why - nested promise chains can be difficult to read and understand. Often, you can achieve the same result by either returning the promise and handling them on a higher level or converting them to an async function.

Example

Before

private pushFileToiOSDevice(
udid: string,
bundleId: string,
destination: string,
filename: string,
contents: string,
): Promise<void> {
return tmpDir({unsafeCleanup: true}).then((dir) => {
const filePath = path.resolve(dir, filename);
promisify(fs.writeFile)(filePath, contents).then(() =>
iosUtil.push(
udid,
filePath,
bundleId,
destination,
this.config.idbPath,
),
);
});
}

After

async pushFileToiOSDevice(
udid: string,
bundleId: string,
destination: string,
filename: string,
contents: string,
): Promise<void> {
const dir = await tmpDir({unsafeCleanup: true});
const filePath = path.resolve(dir, filename);
await fs.writeFile(filePath, contents);
return iosUtil.push(
udid,
filePath,
bundleId,
destination,
this.config.idbPath,
);
}

In addition to less indentation, you also maintain the promise chain here, meaning that you can handle potential errors on the call-side.

flipper/no-console-error-without-context

  • Summary - avoid 'naked' console.error calls. Prefer console.error("Failed to connect open iOS connection socket", e) to console.error(e).
  • Why - we create error tasks internally for every console.error call. It can be hard to find the origin of the error without context.

Example

Before

try {
// ...
} catch (e) {
console.error(e);
}

After

try {
// ...
} catch (e) {
console.error(`Failed to connect to paste host ${hostname}`, e);
}

promise/catch-or-return

  • Summary - ensure that each time a then() is applied to a promise, a catch() is applied as well. Exceptions are made if you are returning that promise. For more details, see catch-or-return.md on GitHub.
  • Why: Unhandled exceptions have no stack trace and will just show up as "Unhandled promise rejection", making them very hard to triage and reproduce. By always ensuring that promises are returned (ensuring they are a chain) or explicitly catching errors, we can improve the user experience by acting more quickly on errors.

Example

Before

function request() {
// If fetch() fails, the exception will bubble to the top.
fetch("https://example.com").then(res => {
doSomethingWith(res);
});
}

After

// Option 1
function request() {
fetch("https://example.com").then(res => {
doSomethingWith(res);
}).catch((e) => {
console.error("Failed to fetch from example.com", e);
});
}

// Option 2
function request() {
// Allow the call-site to handle the error.
return fetch("https://example.com").then(res => {
doSomethingWith(res);
});
}

communist-spelling/communist-spelling

  • Summary - we try to avoid using British spellings for identifiers.
  • Why - this is clearly controversial, but it's very inconvenient when you have to bridge American and British APIs. const greyColour = COLORS.GRAY; is something nobody should have to read or write.

Example

Before

const GreyedOutOverlay = initialiseComponent();

After

const GrayedOutOverlay = initializeComponent();

node/no-sync

  • Summary: Use asynchronous methods wherever possible. More details.
  • Why: Synchronous method calls block the event loop. Even innocuous calls like fs.existsSync() can cause frame drops for users or even long stalls.
  • How to fix it: We have fs-extra as a dependency, which provides Promise-based alternatives for all fs functions. Most often, replacing a sync call with an async call and adding an await is all that's needed.

Before

import fs from 'fs';
function ensureCertsExist() {
if (
!(
fs.existsSync(serverKey) &&
fs.existsSync(serverCert) &&
fs.existsSync(caCert)
)
) {
return generateServerCertificate();
}
}

After

import fsExtra from 'fs-extra';
async function ensureCertsExist() {
const allExist = Promise.all([
fsExtra.exists(serverKey),
fsExtra.exists(serverCert),
fsExtra.exists(caCert),
]).then((exist) => exist.every(Boolean));
if (!allExist) {
return this.generateServerCertificate();
}
}
- + \ No newline at end of file diff --git a/docs/plugins/crash-reporter/overview/index.html b/docs/plugins/crash-reporter/overview/index.html index 7a0c5fbac13..6fa9bee9a8f 100644 --- a/docs/plugins/crash-reporter/overview/index.html +++ b/docs/plugins/crash-reporter/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Crash Reporter plugin shows a notification in Flipper whenever an app crashes.

For Android, clicking on the 'Open in Logs' button jumps to the relevant row in the Logs plugin containing the crash information, as shown in the following screenshots.

UI
- + \ No newline at end of file diff --git a/docs/plugins/crash-reporter/setup/index.html b/docs/plugins/crash-reporter/setup/index.html index e6529aa0ea2..95cef8c6882 100644 --- a/docs/plugins/crash-reporter/setup/index.html +++ b/docs/plugins/crash-reporter/setup/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

You don't have to instantiate the Crash Reporter plugin in your app in order to use its basic functionality.

You can use the Crash Reporter plugin to send notifications for exceptions that are suppressed in your Android application. You could even use it to send notifications when the Litho Error Boundary is triggered.

In order to send custom notifications, take the steps detailed below.

Android

  1. Instantiate and add the plugin in FlipperClient.
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;

client.addPlugin(CrashReporterPlugin.getInstance());
  1. Use the following API to trigger your custom crash notification.
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;

CrashReporterPlugin.getInstance().sendExceptionMessage(Thread.currentThread(), error);
- + \ No newline at end of file diff --git a/docs/plugins/databases/overview/index.html b/docs/plugins/databases/overview/index.html index 609349da5fc..c03b6bdf53f 100644 --- a/docs/plugins/databases/overview/index.html +++ b/docs/plugins/databases/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Databases plugin provides Developers with read-write access to databases.

Plugin functionality

The plugin provides the following functionality:

Examine table structure

The following screenshot shows the structure of the 'ranking' table.

Databases Plugin 1

Execute queries

The following screenshot shows the recordset resulting from execution of the 'statusranking' query.

Databases Plugin 2
- + \ No newline at end of file diff --git a/docs/plugins/databases/setup/index.html b/docs/plugins/databases/setup/index.html index 0837f911842..00e8760b2a1 100644 --- a/docs/plugins/databases/setup/index.html +++ b/docs/plugins/databases/setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Android

To use the Databases plugin, instantiate and add it in FlipperClient, as shown in the following code:

import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;

client.addPlugin(new DatabasesFlipperPlugin(context));

By default, it will list all Sqlite databases returned by the context. If you are storing a Sqlite database somewhere else, you can specify a File to it:

client.addPlugin(new DatabasesFlipperPlugin(new SqliteDatabaseDriver(context, new SqliteDatabaseProvider() {
@Override
public List<File> getDatabaseFiles() {
List<File> databaseFiles = new ArrayList<>();
for (String databaseName : context.databaseList()) {
databaseFiles.add(context.getDatabasePath(databaseName));
}
databaseFiles.add("...path_to_your_db...");
return databaseFiles;
}
})));

If you use a different type of database other than Sqlite, you can implement a driver to be able to access it via Flipper:

client.addPlugin(new DatabasesFlipperPlugin(new DatabaseDriver(context) {
@Override
public List getDatabases() {
return null;
}

@Override
public List<String> getTableNames(DatabaseDescriptor databaseDescriptor) {
return null;
}

@Override
public DatabaseGetTableDataResponse getTableData(DatabaseDescriptor databaseDescriptor, String table, String order, boolean reverse, int start, int count) {
return null;
}

@Override
public DatabaseGetTableStructureResponse getTableStructure(DatabaseDescriptor databaseDescriptor, String table) {
return null;
}

@Override
public DatabaseExecuteSqlResponse executeSQL(DatabaseDescriptor databaseDescriptor, String query) {
return null;
}
}));
- + \ No newline at end of file diff --git a/docs/plugins/device-logs/overview/index.html b/docs/plugins/device-logs/overview/index.html index f8bc24cd0b9..53c2f7752f1 100644 --- a/docs/plugins/device-logs/overview/index.html +++ b/docs/plugins/device-logs/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Logs plugin shows device logs without any additional setup. This is a device plugin, which means it's not tied to any specific app and there is no additional setup needed to see the logs.

The following screenshot shows an example of a device log obtained via the Logs plugin.

Logs plugin

Plugin functionality

Filtering

The search bar can be used to search for logs and act as a filter for certain types.

From the context menu on the table headers, you can show additional information, such as timestamp, PID or TID.

Clicking on a tag, PID or TID in the table filters only for logs with the same value.

Expression Watcher

The Expression Watcher in the sidebar can be used to 'watch' for certain logs to happen and count how often they occur. An expression can be a simple string, or a regular expression, matched against the logs.

When the notify checkbox is enabled, Flipper sends notifications once the log is being processed. This lets you know when the 'watcher' triggered, even if Flipper is in the background.

- + \ No newline at end of file diff --git a/docs/plugins/fresco/overview/index.html b/docs/plugins/fresco/overview/index.html index 589348dd23b..f8317edd345 100644 --- a/docs/plugins/fresco/overview/index.html +++ b/docs/plugins/fresco/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Images plugin enables you to inspect what images were fetched, where they are coming from, and selectively clear caches.

Currently, the plugin supports Fresco as the backend.

Images plugin

Cache Inspector

Images are grouped by the different caching layers they are stored in. The current fill rate of the cache is shown and you can choose to selectively clear caches.

Attribution

Images can be annotated with attributes that can help to determine the context in which an image was loaded and displayed. You can use that information to filter by a particular surface or only inspect images that are in the critical path of your application (such as during a cold start).

Leak Tracking

Dealing with large resources can require special APIs to be used that circumvent usual garbage collection. The plugin enables the tracking of CloseableReferences for Fresco on Android that weren't properly closed, which can help you improve the performance of your app.

- + \ No newline at end of file diff --git a/docs/plugins/fresco/setup/index.html b/docs/plugins/fresco/setup/index.html index c1de5ae7f37..d9d03d01ae3 100644 --- a/docs/plugins/fresco/setup/index.html +++ b/docs/plugins/fresco/setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Currently, the Images plugin only supports Fresco for Android as backend.

If you'd like to see support for other image loading libraries, please post your request in the Flipper Support Workplace group.

Fresco and Android

The Fresco Images plugin is shipped as a separate Maven artifact:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-fresco-plugin:0.30.1'
}

After including the plugin in your dependencies, you can add it to the client:

import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;

client.addPlugin(new FrescoFlipperPlugin());

The FrescoFlipperPlugin constructor offers a range of configuration options that can be useful if you have an advanced setup of Fresco in your application:

FrescoFlipperPlugin(
DebugImageTracker imageTracker,
PlatformBitmapFactory bitmapFactory,
@Nullable FlipperObjectHelper flipperObjectHelper,
DebugMemoryManager memoryManager,
FlipperPerfLogger perfLogger,
@Nullable FrescoFlipperDebugPrefHelper debugPrefHelper,
@Nullable CloseableReferenceLeakTracker closeableReferenceLeakTracker) { ... }

Leak Tracking

The Flipper plugin can help you track down CloseableReferences that have not had close() called on them. However, this can have a negative impact on the performance of your application.

To enable this functionality, you need to create a CloseableReferenceLeakTracker and set it in both your ImagePipelineConfig for Fresco and the FrescoPluginPlugin on creation:

import com.facebook.imagepipeline.debug.FlipperCloseableReferenceLeakTracker;

// ...

FlipperCloseableReferenceLeakTracker leakTracker = new FlipperCloseableReferenceLeakTracker();

new ImagePipelineConfig.Builder()
// ...
.setCloseableReferenceLeakTracker(leakTracker)
.build();


client.addPlugin(new FrescoFlipperPlugin(
new FlipperImageTracker(),
Fresco.getImagePipelineFactory().getPlatformBitmapFactory(),
null,
new NoOpDebugMemoryManager(),
new NoOpFlipperPerfLogger(),
null,
leakTracker));

Attribution

In order to annotate images with the context they are used in, you have to set a caller context when loading the image. This can be any object; for the simplest case, a String will suffice, as shown below:

String callerContext = "my_feature";

// For DraweeViews:
draweeView.setImageURI(uri, callerContext);

// For prefetching:
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.prefetchToDiskCache(imageRequest, callerContext);

// For manually fetching an image:
DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);

If a caller context is supplied, the image will be properly attributed in the Flipper image plugin.

- + \ No newline at end of file diff --git a/docs/plugins/inspector/overview/index.html b/docs/plugins/inspector/overview/index.html index cc6e55315d4..2eafaa3de2b 100644 --- a/docs/plugins/inspector/overview/index.html +++ b/docs/plugins/inspector/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Layout Inspector is useful for a wide variety of debugging scenarios. You can inspect what views the hierarchy is made up of as well as what properties each view has; this is incredibly useful when debugging issues with your product.

In addition to Flipper, the Layout tab supports Litho and ComponentKit components; it integrates with these frameworks to present components in the hierarchy just as if they were native views, exposing all the layout properties, props, and state of the components. The Layout Inspector is further extensible to support other UI frameworks.

If you hover over a view or component in Flipper (as show in the following screenshot), the corresponding item in your app is highlighted. This is perfect for debugging the bounds of your views and making sure you have the correct visual padding.

Layout plugin

Quick edits

In addition to enabling you to view the hierarchy and inspect each item's properties, the Layout Inspector also enables you to edit almost everything, such as layout attributes, background colors, props, and state. This allows you to quickly tweak paddings, margins, and colors until you are happy with them, all without re-compiling. This can save you many hours implementing a new design.

Target mode

You can enable 'target mode' by clicking on the crosshairs icon (see screenshot, above). After which, you can touch any view on the device and the Layout Inspector will jump to the correct position within your layout hierarchy.

tip

Target mode also works with Talkback running.

Accessibility mode (Android-only)

You can enable 'accessibility mode' by clicking on the accessibility icon (see screenshot, above). This shows the accessibility view hierarchy next to the normal hierarchy. In the hierarchy, the currently accessibility-focused view is highlighted in green, and any accessibility-focusable elements have a green icon next to their name. The hierarchy's context menu also allows you to focus accessibility services on certain elements. When selecting an element in one hierarchy, the corresponding element in the other will also be highlighted. The hierarchies expand and collapse in sync and searching through the main hierarchy works in accessibility mode as well.

When accessibility mode is enabled, the sidebar shows special properties that are used by accessibility services to determine their functionality. This includes items such as content-description, clickable, focusable, and long-clickable, among others.

Talkback

The accessibility mode sidebar also includes a panel with properties derived specifically to show Talkback's interpretation of a view (with logic ported over from Google's Talkback source). While generally accurate, this is not guaranteed to be accurate for all situations. It is always better to turn Talkback on for verification.

- + \ No newline at end of file diff --git a/docs/plugins/inspector/setup/index.html b/docs/plugins/inspector/setup/index.html index 8af8037d184..b3a18b1ddcd 100644 --- a/docs/plugins/inspector/setup/index.html +++ b/docs/plugins/inspector/setup/index.html @@ -17,15 +17,15 @@ - +
-

To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.

Android

Standard Android view only

import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

With Litho Support

Litho support is provided via an optional plugin.

You also need to compile in the litho-annotations package, as Flipper reflects on them at runtime. So ensure to not just include them as compileOnly in your gradle configuration:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.259.0'
debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'
// ...
}

If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies.

import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;

// Instead of hard-coding this setting, it's a good practice to tie
// this to a BuildConfig flag, that you only enable for debug builds
// of your application.
ComponentsConfiguration.isDebugModeEnabled = true;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
// This adds Litho capabilities to the layout inspector.
LithoFlipperDescriptors.add(descriptorMapping);

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

Blocking fullscreen views (Android only)

There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary.

Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:

view.setTag(R.id.flipper_skip_view_traversal, true);

Blocking empty view groups (Android only)

If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment.

Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker.

viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);

iOS

Standard UIView Only

To debug layout using Flipper, add the following pod:

pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version

Once you have added the pod, initialise the plugin and add it to the FlipperClient as follows.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>

SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];

With ComponentKit Support

If you want to enable ComponentKit support in the Layout Inspector, you need to add FlipperKit/FlipperKitLayoutComponentKitSupport to your Podfile:

pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version

Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.h>

SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application
withDescriptorMapper: layoutDescriptorMapper]];
- +

To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.

Android

Standard Android view only

import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

With Litho Support

Litho support is provided via an optional plugin.

You also need to compile in the litho-annotations package, as Flipper reflects on them at runtime. So ensure to not just include them as compileOnly in your gradle configuration:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.260.0'
debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'
// ...
}

If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies.

import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;

// Instead of hard-coding this setting, it's a good practice to tie
// this to a BuildConfig flag, that you only enable for debug builds
// of your application.
ComponentsConfiguration.isDebugModeEnabled = true;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
// This adds Litho capabilities to the layout inspector.
LithoFlipperDescriptors.add(descriptorMapping);

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

Blocking fullscreen views (Android only)

There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary.

Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:

view.setTag(R.id.flipper_skip_view_traversal, true);

Blocking empty view groups (Android only)

If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment.

Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker.

viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);

iOS

Standard UIView Only

To debug layout using Flipper, add the following pod:

pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version

Once you have added the pod, initialise the plugin and add it to the FlipperClient as follows.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>

SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];

With ComponentKit Support

If you want to enable ComponentKit support in the Layout Inspector, you need to add FlipperKit/FlipperKitLayoutComponentKitSupport to your Podfile:

pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version

Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.h>

SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application
withDescriptorMapper: layoutDescriptorMapper]];
+ \ No newline at end of file diff --git a/docs/plugins/leak-canary/overview/index.html b/docs/plugins/leak-canary/overview/index.html index 66b8768df11..78add305fea 100644 --- a/docs/plugins/leak-canary/overview/index.html +++ b/docs/plugins/leak-canary/overview/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

The LeakCanary plugin provides Developers with Flipper support for LeakCanary, an open source memory leak detection library.

Leaks detected by LeakCanary appear automatically in Flipper. Each leak displays a hierarchy of objects, beginning with the garbage collector root and ending at the leaked class. Selecting any object in this list displays contents of the object's various fields.

- + \ No newline at end of file diff --git a/docs/plugins/leak-canary/setup/index.html b/docs/plugins/leak-canary/setup/index.html index bf14dfcb72b..8a722cef1fe 100644 --- a/docs/plugins/leak-canary/setup/index.html +++ b/docs/plugins/leak-canary/setup/index.html @@ -17,15 +17,15 @@ - +
-

To setup the LeakCanary plugin, take the following steps:

  1. Ensure that you have an explicit dependency in your application's build.gradle including the plugin dependency, such as is shown in the following snippet:
dependencies {
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.259.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}
  1. Update your the onCreate method in you Application to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin

...

override fun onCreate() {
super.onCreate()

/*
set the flipper listener in leak canary config
*/
LeakCanary.config = LeakCanary.config.run {
copy(eventListeners = eventListeners + FlipperLeakEventListener())
}

SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
/*
add leak canary plugin to flipper
*/
client.addPlugin(LeakCanary2FlipperPlugin())
client.start()
}
}
- +

To setup the LeakCanary plugin, take the following steps:

  1. Ensure that you have an explicit dependency in your application's build.gradle including the plugin dependency, such as is shown in the following snippet:
dependencies {
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.260.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}
  1. Update your the onCreate method in you Application to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin

...

override fun onCreate() {
super.onCreate()

/*
set the flipper listener in leak canary config
*/
LeakCanary.config = LeakCanary.config.run {
copy(eventListeners = eventListeners + FlipperLeakEventListener())
}

SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
/*
add leak canary plugin to flipper
*/
client.addPlugin(LeakCanary2FlipperPlugin())
client.start()
}
}
+ \ No newline at end of file diff --git a/docs/plugins/navigation/overview/index.html b/docs/plugins/navigation/overview/index.html index b4153dab47a..8fa85b271c3 100644 --- a/docs/plugins/navigation/overview/index.html +++ b/docs/plugins/navigation/overview/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

The Navigation Plugin enables users to quickly navigate to deep links within their mobile applications to help speed up the development cycle. The plugin is designed to integrate easily within your existing navigation framework or as a standalone tool.

Users can bookmark deep-links and jump to them via the button in the toolbar, as shown in the following screenshot. Navigation events within the app can also be logged to Flipper, which enables the user to view past navigation events and jump straight to them or export the navigation events for reporting.

- + \ No newline at end of file diff --git a/docs/plugins/navigation/setup/index.html b/docs/plugins/navigation/setup/index.html index 43762477f99..64f3662e2c8 100644 --- a/docs/plugins/navigation/setup/index.html +++ b/docs/plugins/navigation/setup/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Android

First, add the Navigation plugin to your Flipper client instance:

import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin;

final FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(NavigationFlipperPlugin.getInstance());

Navigation events in the app can then be recorded by calling sendNavigationEvent method of the NavigationFlipperPlugin instance from anywhere in the app. This enables the Navigation Plugin to be integrated into existing navigation frameworks.

The Navigation Plugin can be used with built-in Deep Links for Android.

To deep link to an activity, edit the AndroidManifest.xml and add the intent filter for the given activity, as follows:

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="flipper" android:host="deep_link_activity" />
</intent-filter>

This enables the user to jump to flipper://deep_link_activity within Flipper.

To log that navigation event in flipper, you can send the navigation event in the Activity's onCreate method, as follows:

public class DeepLinkActivity extends AppCompatActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NavigationFlipperPlugin.getInstance().sendNavigationEvent("flipper://deep_link_activity/");
...

Third party solutions

The Navigation Plugin can easily be integrated into a third-party navigation framework.

DeepLinkDispatch will work out of the box with Flipper for navigating to links, including support for url parameters.

To add logging, simply add a BroadcastReceiver to your app that is called on any incoming deep links:

public class DeepLinkReceiver extends BroadcastReceiver {
private static final String TAG = "DeepLinkReceiver";

@Override public void onReceive(Context context, Intent intent) {
String deepLinkUri = intent.getStringExtra(DeepLinkHandler.EXTRA_URI);
if (intent.getBooleanExtra(DeepLinkHandler.EXTRA_SUCCESSFUL, false)) {
NavigationFlipperPlugin.getInstance().sendNavigationEvent(deepLinkUri);
}
}
}

public class DeepLinkApplication extends Application {
@Override public void onCreate() {
super.onCreate();
IntentFilter intentFilter = new IntentFilter(DeepLinkHandler.ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(new DeepLinkReceiver(), intentFilter);
}
}
- + \ No newline at end of file diff --git a/docs/plugins/network/overview/index.html b/docs/plugins/network/overview/index.html index ec9762679ce..42cd94665ac 100644 --- a/docs/plugins/network/overview/index.html +++ b/docs/plugins/network/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Network plugin provides the Network Inspector, which is used to inspect outgoing network traffic in your apps. You can easily browse all requests being made and their responses. The plugin also supports gzipped responses.

All requests sent from the device are listed in the plugin. Clicking on a request shows details such as headers and body. You can filter the table for domain, method or status by clicking on the corresponding value in the table.

The following screenshot shows the Network Inspector in action.

Network plugin
- + \ No newline at end of file diff --git a/docs/plugins/network/setup/index.html b/docs/plugins/network/setup/index.html index a5b1a8460ac..d0d22602eee 100644 --- a/docs/plugins/network/setup/index.html +++ b/docs/plugins/network/setup/index.html @@ -17,15 +17,15 @@ - +
-

To use the Network plugin, you need to add the plugin to your Flipper client instance.

Android

The network plugin is shipped as a separate Maven artifact, as follows:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.259.0'
}

Once added to your dependencies, you can instantiate the plugin and add it to the client:

import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;

NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);

OkHttp Integration

If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:

import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;

new OkHttpClient.Builder()
.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))
.build();

As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic.

Protobuf / Retrofit Integration

If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'
}

Then call SendProtobufToFlipperFromRetrofit for each service class:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit

SendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)

iOS

To enable network inspection, add the following pod to your Podfile:

pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way by updating AppDelegate.m:

#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>

[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];

Protobuf + Retrofit Setup

Gradle Dependencies

Ensure that you already have an explicit dependency in your application's build.gradle including the plugin dependency, as shown in the following example:

dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"

// update version below to match latest Flipper client app
debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"
}

Sending Retrofit Service

If you have a Retrofit service interface PersonService which has Protobuf body or return types then at the time you create your implementation, call the plugin with your baseURL and service class, as follows:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit
...
val personService = retrofit.create(PersonService::class.java)
SendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)
- +

To use the Network plugin, you need to add the plugin to your Flipper client instance.

Android

The network plugin is shipped as a separate Maven artifact, as follows:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.260.0'
}

Once added to your dependencies, you can instantiate the plugin and add it to the client:

import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;

NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);

OkHttp Integration

If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:

import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;

new OkHttpClient.Builder()
.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))
.build();

As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic.

Protobuf / Retrofit Integration

If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'
}

Then call SendProtobufToFlipperFromRetrofit for each service class:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit

SendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)

iOS

To enable network inspection, add the following pod to your Podfile:

pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way by updating AppDelegate.m:

#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>

[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];

Protobuf + Retrofit Setup

Gradle Dependencies

Ensure that you already have an explicit dependency in your application's build.gradle including the plugin dependency, as shown in the following example:

dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"

// update version below to match latest Flipper client app
debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"
}

Sending Retrofit Service

If you have a Retrofit service interface PersonService which has Protobuf body or return types then at the time you create your implementation, call the plugin with your baseURL and service class, as follows:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit
...
val personService = retrofit.create(PersonService::class.java)
SendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)
+ \ No newline at end of file diff --git a/docs/plugins/preferences/overview/index.html b/docs/plugins/preferences/overview/index.html index d461c0b4567..28d1a85e01e 100644 --- a/docs/plugins/preferences/overview/index.html +++ b/docs/plugins/preferences/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Shared Preferences Viewer plugin enables you to easily inspect and modify the data contained within your app's shared preferences, as shown in the following screenshot.

Shared Preferences Plugin

All changes to the given shared preference file automatically appear in Flipper.

You may also edit the values in Flipper and have them synced to your device. This can be done by clicking on the value of the specific key you wish to edit, editing the value and then pressing enter.

- + \ No newline at end of file diff --git a/docs/plugins/preferences/setup/index.html b/docs/plugins/preferences/setup/index.html index 2c96fa7584e..a996af4e6a2 100644 --- a/docs/plugins/preferences/setup/index.html +++ b/docs/plugins/preferences/setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Shared Preferences Viewer plugin is available for both Android and iOS.

Android

import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;

client.addPlugin(
new SharedPreferencesFlipperPlugin(context, "my_shared_preference_file"));

iOS

If you want to use the Shared Preferences plugin, you need to add FlipperKit/FlipperKitUserDefaultsPlugin to your Podfile.

pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way:

#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>

[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:@"your_suitename"]];
- + \ No newline at end of file diff --git a/docs/plugins/sandbox/overview/index.html b/docs/plugins/sandbox/overview/index.html index f51bed59fa6..27c6ac6af60 100644 --- a/docs/plugins/sandbox/overview/index.html +++ b/docs/plugins/sandbox/overview/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

The Sandbox plugin enables Developers to test changes in their apps by pointing them to a sandbox environment. It provides a simple UI to set and modify the URL to a development host that acts as a sandbox directly on the desktop, which prevents you from entering potentially long and complicated URLs inside your app.

- + \ No newline at end of file diff --git a/docs/plugins/sandbox/setup/index.html b/docs/plugins/sandbox/setup/index.html index ae652dfe78d..e075e31a753 100644 --- a/docs/plugins/sandbox/setup/index.html +++ b/docs/plugins/sandbox/setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

To use the Sandbox plugin, you need to add the plugin to your Flipper client instance.

Android

import com.facebook.flipper.plugins.sandbox.SandboxFlipperPlugin;
import com.facebook.flipper.plugins.sandbox.SandboxFlipperPluginStrategy;

final SandboxFlipperPluginStrategy strategy = getStrategy(); // Your strategy goes here
client.addPlugin(new SandboxFlipperPlugin(strategy));
- + \ No newline at end of file diff --git a/docs/plugins/ui-debugger/overview/index.html b/docs/plugins/ui-debugger/overview/index.html index b776db56879..d37c04e9593 100644 --- a/docs/plugins/ui-debugger/overview/index.html +++ b/docs/plugins/ui-debugger/overview/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

The UIDebugger is a replacement for the Layout inspector. It streams the full hierarchy of the running app to flipper desktop in near real time. We display a 2D visualization with all of your view and component bounds overlayed on top. You can focus on a particular view or component from the context menu. Additionally, you can pause incoming updates to focus on a particular frame.

You can inspect what views the hierarchy is made up of as well as what properties each view has; this is useful when debugging issues with your product.

We currently support the following platforms and frameworks:

It integrates with these frameworks to present components in the hierarchy just as if they were native views, exposing all the layout properties, props, and state of the components.

UIDebugger
- + \ No newline at end of file diff --git a/docs/setup/plugins/crash-reporter/index.html b/docs/setup/plugins/crash-reporter/index.html index 682584e77c0..cdf43152177 100644 --- a/docs/setup/plugins/crash-reporter/index.html +++ b/docs/setup/plugins/crash-reporter/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Crash Reporter Plugin Setup

You don't have to instantiate the Crash Reporter plugin in your app in order to use its basic functionality.

You can use the Crash Reporter plugin to send notifications for exceptions that are suppressed in your Android application. You could even use it to send notifications when the Litho Error Boundary is triggered.

In order to send custom notifications, take the steps detailed below.

Android

  1. Instantiate and add the plugin in FlipperClient.
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;

client.addPlugin(CrashReporterPlugin.getInstance());
  1. Use the following API to trigger your custom crash notification.
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;

CrashReporterPlugin.getInstance().sendExceptionMessage(Thread.currentThread(), error);
- + \ No newline at end of file diff --git a/docs/setup/plugins/databases/index.html b/docs/setup/plugins/databases/index.html index e2395179e72..1668b93c122 100644 --- a/docs/setup/plugins/databases/index.html +++ b/docs/setup/plugins/databases/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Databases Plugin Setup

Android

To use the Databases plugin, instantiate and add it in FlipperClient, as shown in the following code:

import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;

client.addPlugin(new DatabasesFlipperPlugin(context));

By default, it will list all Sqlite databases returned by the context. If you are storing a Sqlite database somewhere else, you can specify a File to it:

client.addPlugin(new DatabasesFlipperPlugin(new SqliteDatabaseDriver(context, new SqliteDatabaseProvider() {
@Override
public List<File> getDatabaseFiles() {
List<File> databaseFiles = new ArrayList<>();
for (String databaseName : context.databaseList()) {
databaseFiles.add(context.getDatabasePath(databaseName));
}
databaseFiles.add("...path_to_your_db...");
return databaseFiles;
}
})));

If you use a different type of database other than Sqlite, you can implement a driver to be able to access it via Flipper:

client.addPlugin(new DatabasesFlipperPlugin(new DatabaseDriver(context) {
@Override
public List getDatabases() {
return null;
}

@Override
public List<String> getTableNames(DatabaseDescriptor databaseDescriptor) {
return null;
}

@Override
public DatabaseGetTableDataResponse getTableData(DatabaseDescriptor databaseDescriptor, String table, String order, boolean reverse, int start, int count) {
return null;
}

@Override
public DatabaseGetTableStructureResponse getTableStructure(DatabaseDescriptor databaseDescriptor, String table) {
return null;
}

@Override
public DatabaseExecuteSqlResponse executeSQL(DatabaseDescriptor databaseDescriptor, String query) {
return null;
}
}));
- + \ No newline at end of file diff --git a/docs/setup/plugins/fresco/index.html b/docs/setup/plugins/fresco/index.html index 308b0e27a43..f21a2e890c4 100644 --- a/docs/setup/plugins/fresco/index.html +++ b/docs/setup/plugins/fresco/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Images Plugin Setup

Currently, the Images plugin only supports Fresco for Android as backend.

If you'd like to see support for other image loading libraries, please post your request in the Flipper Support Workplace group.

Fresco and Android

The Fresco Images plugin is shipped as a separate Maven artifact:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-fresco-plugin:0.30.1'
}

After including the plugin in your dependencies, you can add it to the client:

import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;

client.addPlugin(new FrescoFlipperPlugin());

The FrescoFlipperPlugin constructor offers a range of configuration options that can be useful if you have an advanced setup of Fresco in your application:

FrescoFlipperPlugin(
DebugImageTracker imageTracker,
PlatformBitmapFactory bitmapFactory,
@Nullable FlipperObjectHelper flipperObjectHelper,
DebugMemoryManager memoryManager,
FlipperPerfLogger perfLogger,
@Nullable FrescoFlipperDebugPrefHelper debugPrefHelper,
@Nullable CloseableReferenceLeakTracker closeableReferenceLeakTracker) { ... }

Leak Tracking

The Flipper plugin can help you track down CloseableReferences that have not had close() called on them. However, this can have a negative impact on the performance of your application.

To enable this functionality, you need to create a CloseableReferenceLeakTracker and set it in both your ImagePipelineConfig for Fresco and the FrescoPluginPlugin on creation:

import com.facebook.imagepipeline.debug.FlipperCloseableReferenceLeakTracker;

// ...

FlipperCloseableReferenceLeakTracker leakTracker = new FlipperCloseableReferenceLeakTracker();

new ImagePipelineConfig.Builder()
// ...
.setCloseableReferenceLeakTracker(leakTracker)
.build();


client.addPlugin(new FrescoFlipperPlugin(
new FlipperImageTracker(),
Fresco.getImagePipelineFactory().getPlatformBitmapFactory(),
null,
new NoOpDebugMemoryManager(),
new NoOpFlipperPerfLogger(),
null,
leakTracker));

Attribution

In order to annotate images with the context they are used in, you have to set a caller context when loading the image. This can be any object; for the simplest case, a String will suffice, as shown below:

String callerContext = "my_feature";

// For DraweeViews:
draweeView.setImageURI(uri, callerContext);

// For prefetching:
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.prefetchToDiskCache(imageRequest, callerContext);

// For manually fetching an image:
DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);

If a caller context is supplied, the image will be properly attributed in the Flipper image plugin.

- + \ No newline at end of file diff --git a/docs/setup/plugins/inspector/index.html b/docs/setup/plugins/inspector/index.html index a9a3c5134f0..2e3657d9652 100644 --- a/docs/setup/plugins/inspector/index.html +++ b/docs/setup/plugins/inspector/index.html @@ -17,15 +17,15 @@ - +
-

Layout Plugin Setup

To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.

Android

Standard Android view only

import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

With Litho Support

Litho support is provided via an optional plugin.

You also need to compile in the litho-annotations package, as Flipper reflects on them at runtime. So ensure to not just include them as compileOnly in your gradle configuration:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.259.0'
debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'
// ...
}

If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies.

import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;

// Instead of hard-coding this setting, it's a good practice to tie
// this to a BuildConfig flag, that you only enable for debug builds
// of your application.
ComponentsConfiguration.isDebugModeEnabled = true;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
// This adds Litho capabilities to the layout inspector.
LithoFlipperDescriptors.add(descriptorMapping);

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

Blocking fullscreen views (Android only)

There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary.

Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:

view.setTag(R.id.flipper_skip_view_traversal, true);

Blocking empty view groups (Android only)

If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment.

Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker.

viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);

iOS

Standard UIView Only

To debug layout using Flipper, add the following pod:

pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version

Once you have added the pod, initialise the plugin and add it to the FlipperClient as follows.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>

SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];

With ComponentKit Support

If you want to enable ComponentKit support in the Layout Inspector, you need to add FlipperKit/FlipperKitLayoutComponentKitSupport to your Podfile:

pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version

Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.h>

SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application
withDescriptorMapper: layoutDescriptorMapper]];
- +

Layout Plugin Setup

To use the Layout Inspector plugin, you need to add the plugin to your Flipper client instance.

Android

Standard Android view only

import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

With Litho Support

Litho support is provided via an optional plugin.

You also need to compile in the litho-annotations package, as Flipper reflects on them at runtime. So ensure to not just include them as compileOnly in your gradle configuration:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.260.0'
debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'
// ...
}

If you want to enable Litho support in the layout inspector, you need to augment the descriptor with Litho-specific settings and add some addition dependencies.

import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.litho.LithoFlipperDescriptors;

// Instead of hard-coding this setting, it's a good practice to tie
// this to a BuildConfig flag, that you only enable for debug builds
// of your application.
ComponentsConfiguration.isDebugModeEnabled = true;

final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
// This adds Litho capabilities to the layout inspector.
LithoFlipperDescriptors.add(descriptorMapping);

client.addPlugin(new InspectorFlipperPlugin(mApplicationContext, descriptorMapping));

Blocking fullscreen views (Android only)

There is an issue that if you have a view that occupies a big part of the screen but draws nothing, and its Z-position is higher than your main content, then selecting view/component through the Layout Inspector doesn't function as you intended. This is because it always hits that transparent view, therefore, you need to manually navigate to the view you need: this is time-consuming and should not be necessary.

Add the following tag to your view to skip it from Flipper's view picker. The view is still shown in the layout hierarchy but is selected while using the view picker:

view.setTag(R.id.flipper_skip_view_traversal, true);

Blocking empty view groups (Android only)

If you have a ViewGroup that only occasionally has visible children, you may find it helpful to block its traversal when it's empty or has no visible children. For example, you might have a FragmentContainerView that currently has no visible fragment.

Add the following tag to your view group to skip it from Flipper's view picker only when it has zero children, or none of its children are currently visible. The views will still be shown in the layout hierarchy, but they will not be selected while using the view picker.

viewGroup.setTag(R.id.flipper_skip_empty_view_group_traversal, true);

iOS

Standard UIView Only

To debug layout using Flipper, add the following pod:

pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version

Once you have added the pod, initialise the plugin and add it to the FlipperClient as follows.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>

SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]];

With ComponentKit Support

If you want to enable ComponentKit support in the Layout Inspector, you need to add FlipperKit/FlipperKitLayoutComponentKitSupport to your Podfile:

pod 'FlipperKit/FlipperKitLayoutComponentKitSupport', '~>' + flipperkit_version

Once you have added the pod you will then need to augment the descriptor with Componentkit-specific settings as shown below.

#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.h>

SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
[FlipperKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper];
[client addPlugin: [[FlipperKitLayoutPlugin alloc] initWithRootNode: application
withDescriptorMapper: layoutDescriptorMapper]];
+ \ No newline at end of file diff --git a/docs/setup/plugins/leak-canary/index.html b/docs/setup/plugins/leak-canary/index.html index 86f0d9ff8ef..c5a75a309a9 100644 --- a/docs/setup/plugins/leak-canary/index.html +++ b/docs/setup/plugins/leak-canary/index.html @@ -17,15 +17,15 @@ - +
-

LeakCanary Plugin Setup

To setup the LeakCanary plugin, take the following steps:

  1. Ensure that you have an explicit dependency in your application's build.gradle including the plugin dependency, such as is shown in the following snippet:
dependencies {
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.259.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}
  1. Update your the onCreate method in you Application to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin

...

override fun onCreate() {
super.onCreate()

/*
set the flipper listener in leak canary config
*/
LeakCanary.config = LeakCanary.config.run {
copy(eventListeners = eventListeners + FlipperLeakEventListener())
}

SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
/*
add leak canary plugin to flipper
*/
client.addPlugin(LeakCanary2FlipperPlugin())
client.start()
}
}
- +

LeakCanary Plugin Setup

To setup the LeakCanary plugin, take the following steps:

  1. Ensure that you have an explicit dependency in your application's build.gradle including the plugin dependency, such as is shown in the following snippet:
dependencies {
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.260.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}
  1. Update your the onCreate method in you Application to add the LeakCanary2 plugin to Flipper and the Flipper listener to LeakCanary:
import com.facebook.flipper.plugins.leakcanary2.FlipperLeakEventListener
import com.facebook.flipper.plugins.leakcanary2.LeakCanary2FlipperPlugin

...

override fun onCreate() {
super.onCreate()

/*
set the flipper listener in leak canary config
*/
LeakCanary.config = LeakCanary.config.run {
copy(eventListeners = eventListeners + FlipperLeakEventListener())
}

SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
/*
add leak canary plugin to flipper
*/
client.addPlugin(LeakCanary2FlipperPlugin())
client.start()
}
}
+ \ No newline at end of file diff --git a/docs/setup/plugins/navigation/index.html b/docs/setup/plugins/navigation/index.html index f5c1e4ef0c1..29c7e4be236 100644 --- a/docs/setup/plugins/navigation/index.html +++ b/docs/setup/plugins/navigation/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Navigation Plugin Setup

Android

First, add the Navigation plugin to your Flipper client instance:

import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin;

final FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(NavigationFlipperPlugin.getInstance());

Navigation events in the app can then be recorded by calling sendNavigationEvent method of the NavigationFlipperPlugin instance from anywhere in the app. This enables the Navigation Plugin to be integrated into existing navigation frameworks.

The Navigation Plugin can be used with built-in Deep Links for Android.

To deep link to an activity, edit the AndroidManifest.xml and add the intent filter for the given activity, as follows:

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="flipper" android:host="deep_link_activity" />
</intent-filter>

This enables the user to jump to flipper://deep_link_activity within Flipper.

To log that navigation event in flipper, you can send the navigation event in the Activity's onCreate method, as follows:

public class DeepLinkActivity extends AppCompatActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NavigationFlipperPlugin.getInstance().sendNavigationEvent("flipper://deep_link_activity/");
...

Third party solutions

The Navigation Plugin can easily be integrated into a third-party navigation framework.

DeepLinkDispatch will work out of the box with Flipper for navigating to links, including support for url parameters.

To add logging, simply add a BroadcastReceiver to your app that is called on any incoming deep links:

public class DeepLinkReceiver extends BroadcastReceiver {
private static final String TAG = "DeepLinkReceiver";

@Override public void onReceive(Context context, Intent intent) {
String deepLinkUri = intent.getStringExtra(DeepLinkHandler.EXTRA_URI);
if (intent.getBooleanExtra(DeepLinkHandler.EXTRA_SUCCESSFUL, false)) {
NavigationFlipperPlugin.getInstance().sendNavigationEvent(deepLinkUri);
}
}
}

public class DeepLinkApplication extends Application {
@Override public void onCreate() {
super.onCreate();
IntentFilter intentFilter = new IntentFilter(DeepLinkHandler.ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(new DeepLinkReceiver(), intentFilter);
}
}
- + \ No newline at end of file diff --git a/docs/setup/plugins/network/index.html b/docs/setup/plugins/network/index.html index 25b6a1aa717..c946cf037f1 100644 --- a/docs/setup/plugins/network/index.html +++ b/docs/setup/plugins/network/index.html @@ -17,15 +17,15 @@ - +
-

Network Plugin Setup

To use the Network plugin, you need to add the plugin to your Flipper client instance.

Android

The network plugin is shipped as a separate Maven artifact, as follows:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.259.0'
}

Once added to your dependencies, you can instantiate the plugin and add it to the client:

import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;

NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);

OkHttp Integration

If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:

import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;

new OkHttpClient.Builder()
.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))
.build();

As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic.

Protobuf / Retrofit Integration

If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'
}

Then call SendProtobufToFlipperFromRetrofit for each service class:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit

SendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)

iOS

To enable network inspection, add the following pod to your Podfile:

pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way by updating AppDelegate.m:

#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>

[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];

Protobuf + Retrofit Setup

Gradle Dependencies

Ensure that you already have an explicit dependency in your application's build.gradle including the plugin dependency, as shown in the following example:

dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"

// update version below to match latest Flipper client app
debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"
}

Sending Retrofit Service

If you have a Retrofit service interface PersonService which has Protobuf body or return types then at the time you create your implementation, call the plugin with your baseURL and service class, as follows:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit
...
val personService = retrofit.create(PersonService::class.java)
SendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)
- +

Network Plugin Setup

To use the Network plugin, you need to add the plugin to your Flipper client instance.

Android

The network plugin is shipped as a separate Maven artifact, as follows:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.260.0'
}

Once added to your dependencies, you can instantiate the plugin and add it to the client:

import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;

NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);

OkHttp Integration

If you are using the popular OkHttp library, you can use the Interceptors system to automatically hook into your existing stack, as shown in the following snippet:

import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;

new OkHttpClient.Builder()
.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin))
.build();

As interceptors can modify the request and response, add the Flipper interceptor after all others to get an accurate view of the network traffic.

Protobuf / Retrofit Integration

If you are using Retrofit with Protobuf request or response types, you can setup automatic decoding so that the network inspector can display a human readable payload. First you must add the separate dependency:

dependencies {
debugImplementation 'com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.91.2'
}

Then call SendProtobufToFlipperFromRetrofit for each service class:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit

SendProtobufToFlipperFromRetrofit("https://baseurl.com/", MyApiService::class.java)

iOS

To enable network inspection, add the following pod to your Podfile:

pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way by updating AppDelegate.m:

#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>

[[FlipperClient sharedClient] addPlugin: [[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];

Protobuf + Retrofit Setup

Gradle Dependencies

Ensure that you already have an explicit dependency in your application's build.gradle including the plugin dependency, as shown in the following example:

dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"

// update version below to match latest Flipper client app
debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"
}

Sending Retrofit Service

If you have a Retrofit service interface PersonService which has Protobuf body or return types then at the time you create your implementation, call the plugin with your baseURL and service class, as follows:

import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit
...
val personService = retrofit.create(PersonService::class.java)
SendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)
+ \ No newline at end of file diff --git a/docs/setup/plugins/preferences/index.html b/docs/setup/plugins/preferences/index.html index 8141ab41905..8a632ec09c4 100644 --- a/docs/setup/plugins/preferences/index.html +++ b/docs/setup/plugins/preferences/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Shared Preferences Viewer Plugin Setup

The Shared Preferences Viewer plugin is available for both Android and iOS.

Android

import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;

client.addPlugin(
new SharedPreferencesFlipperPlugin(context, "my_shared_preference_file"));

iOS

If you want to use the Shared Preferences plugin, you need to add FlipperKit/FlipperKitUserDefaultsPlugin to your Podfile.

pod 'FlipperKit/FlipperKitUserDefaultsPlugin', '~>' + flipperkit_version

Initialize the plugin in the following way:

#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>

[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:@"your_suitename"]];
- + \ No newline at end of file diff --git a/docs/setup/plugins/sandbox/index.html b/docs/setup/plugins/sandbox/index.html index ef44eb740a9..ee1f9b75bf9 100644 --- a/docs/setup/plugins/sandbox/index.html +++ b/docs/setup/plugins/sandbox/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Sandbox Plugin Setup

To use the Sandbox plugin, you need to add the plugin to your Flipper client instance.

Android

import com.facebook.flipper.plugins.sandbox.SandboxFlipperPlugin;
import com.facebook.flipper.plugins.sandbox.SandboxFlipperPluginStrategy;

final SandboxFlipperPluginStrategy strategy = getStrategy(); // Your strategy goes here
client.addPlugin(new SandboxFlipperPlugin(strategy));
- + \ No newline at end of file diff --git a/docs/stetho/index.html b/docs/stetho/index.html index 07acd095a79..1ac3ff20580 100644 --- a/docs/stetho/index.html +++ b/docs/stetho/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Stetho Guidance

In 2015, Facebook (now Meta) introduced Stetho, an Android debugging bridge built on top of Chrome DevTools. While it was a valuable tool to members of the community, it was felt that it was limited in what could be achieved with it. Stetho is Android-only and while Chrome DevTools (which was built for Web Developers) provides a nice foundation to build upon, their capability was also limited.

Flipper has been built as a standalone app that provides functionality such as handling adb connections and supporting iOS, which weren't easily achievable with Stetho.

Flipper has been built to create a platform that provides all the flexibility needed to build more advanced features and support for iOS. One of Flipper's core concepts is its extensibility using plugins. Plugins are written in React and provide a set of ready-to-use UI components that allow Developers to build great plugin UIs with a few lines of code. While offering many new features, Flipper also already covers most of the features provided by Stetho, such network and layout inspection and an advanced log viewer.

Meta is committed to continuously improving Flipper with new features and plugins.

Meta is aware that not all Stetho features are currently supported by Flipper. If you are using a particular feature of Stetho which isn't supported by Flipper, please post details of your use case in the Flipper Support Workplace group. Meta is confident that Flipper will work well for most use cases and are more than happy to accept contributions from the open-source community.

Stetho will continue to be used, enabling you to choose the tool that fits your needs best.

- + \ No newline at end of file diff --git a/docs/tutorial/android/index.html b/docs/tutorial/android/index.html index d2b2a5efec1..371c8419d17 100644 --- a/docs/tutorial/android/index.html +++ b/docs/tutorial/android/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Building an Android Plugin

Android Tutorial App

For the purpose of the tutorial, it's assumed you already have an existing Android application in which you have a feed or list of items.

Part of what makes Flipper so useful is allowing users to inspect the internals of their app. This part of the tutorial is concerned with how to make data available to the Flipper desktop app, which, for the sake of this tutorial, is called 'Sea-mammals' (an app based on sea mammals).

note

You can find the source code for this tutorial on GitHub.

Creating a Plugin

On Android, a Flipper plugin is a class that implements the FlipperPlugin interface.

The interface consists of the following four methods:

  • getId() -> String - specify a unique string so the JavaScript side knows where to talk to. This must match the name attribute in the package.json, which is examined later in this tutorial.
  • onConnect(FlipperConnection) - this method is called when the desktop app connects to the mobile client and is ready to receive or send data.
  • onDisconnect() - sets the connection to nil.
  • runInBackground() -> Boolean - unless this is true, only the currently selected plugin in the Flipper UI can communicate with the device.

The following code shows the implementation of these methods for the Sea-mammals app:

import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.sample.tutorial.MarineMammals

class SeaMammalFlipperPlugin : FlipperPlugin {
private var connection: FlipperConnection? = null

override fun getId(): String = "sea-mammals"

override fun onConnect(connection: FlipperConnection?) {
this.connection = connection

MarineMammals.list.mapIndexed { index, (title, picture_url) ->
FlipperObject.Builder()
.put("id", index)
.put("title", title)
.put("url", picture_url).build()
}.forEach(this::newRow)
}

override fun onDisconnect() {
connection = null
}

override fun runInBackground(): Boolean = false

private fun newRow(row: FlipperObject) {
connection?.send("newRow", row)
}
}

See SeaMammalFlipperPlugin.kt

The two interesting items here are onConnect and newRow, which sends a message to the desktop app and is identified with the same name 'newRow'.

For the sea-mammals app, there is a static data source. However, in real life, you'll likely dynamically receive new data as the user interacts with the app. So, while in the Sea-mammals app, the data is sent all at once with onConnect, in real life, you'd set up a listener to call newRow as new data arrives.

You may have notices that rather than sending random Objects over the wire, FlipperObjects are used. A FlipperObject works similar to a JSON dictionary and has a limited set of supported types like strings, integers and arrays.

note

Before sending an object from your native app to the desktop, you first need to break it down into FlipperObject-serializable parts.

Registering your plugin

Now, all you need to do is let Flipper know about your new plugin. You do this by calling addPlugin on your FlipperClient, which is normally created at application startup.

val flipperClient = AndroidFlipperClient.getInstance(this)
// Add all sorts of other amazing plugins here ...
flipperClient.addPlugin(SeaMammalFlipperPlugin())
flipperClient.start()

See TutorialApplication.kt

Next step

When starting your application, Flipper tells the desktop application about the plugin it supports (including 'Sea-mammals') and looks for a corresponding JavaScript plugin by that name.

- + \ No newline at end of file diff --git a/docs/tutorial/intro/index.html b/docs/tutorial/intro/index.html index 1db5a9b5ad0..cec9e6dfbab 100644 --- a/docs/tutorial/intro/index.html +++ b/docs/tutorial/intro/index.html @@ -17,7 +17,7 @@ - + @@ -28,7 +28,7 @@ In addition to building plugins for the existing platforms, you can also extend the capabilities of Flipper to other platforms by conforming to the FlipperClient API. After this, you can make use of the existing desktop plugins by writing client plugins that conform to the same API.

In this tutorial, you'll learn how easy it is to build a Flipper plugin for Android and iOS that extracts data from your native application and displays it in a desktop app. You'll then be guided through the process of converting a basic table plugin into a full plugin with custom UI components.

At the end of the tutorial, you'll have created a plugin that looks like the following screenshot example.

Android App Tutorial

Before you get started, here are two terms you'll see frequently throughout this tutorial:

  • Desktop app - Web Application and underlying Flipper runtime which you run on your desktop.
  • Mobile client - mobile app running most likely on a phone or other mobile device: it connects to the desktop app.
- + \ No newline at end of file diff --git a/docs/tutorial/ios/index.html b/docs/tutorial/ios/index.html index 72ffb255fb7..43a879c5de8 100644 --- a/docs/tutorial/ios/index.html +++ b/docs/tutorial/ios/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Building an iOS Plugin

iOS Tutorial App

For the purpose of the tutorial, it's assumed you already have an existing iOS application in which you have a feed or list of items.

Part of what makes Flipper so useful is allowing users to inspect the internals of their app. This part of the tutorial is concerned with how to make data available to the Flipper desktop app, which, for the sake of this tutorial, is called 'Sea-mammals' (an app based on sea mammals).

note

You can find the source code for this tutorial on GitHub.

Creating a plugin

On iOS, a Flipper plugin is a class that implements the FlipperPlugin interface.

The interface consists of four methods:

  • (NSString *)identifier - specify a unique string so the JavaScript side knows where to talk to. This must match the name attribute in the package.json, which is examined later in the tutorial.
  • (void)didConnect:(id<FlipperConnection>)connection - this method is called when the desktop client connects and is ready to receive or send data.
  • (void)didDisconnect - sets connection to nil.
  • (BOOL)runInBackground - unless this is true, only the currently selected plugin in the Flipper UI can communicate with the device. Its an optional method which you can override. The default value used is false.

The following code shows the implementation of these methods for the Sea-mammals app:

import Foundation
import FlipperKit

class SeaMammalsPlugin: NSObject, FlipperPlugin {
var connection: FlipperConnection? = nil
let mammals: [MarineMammal]

init(_ marineMammals: [MarineMammal]) {
mammals = marineMammals
}

func identifier() -> String! {
return "Sea-mammals"
}

func didConnect(_ connection: FlipperConnection!) {
self.connection = connection
for (index, mammal) in mammals.enumerated() {
connection.send("newRow", withParams: ["id": index, "title": mammal.name, "url": mammal.image.absoluteString])
}
}

func didDisconnect() {
connection = nil;
}
}

There are two items of interest here: didConnect and connection.send, which sends a message to the desktop app and is identified with the name newRow.

For the Sea-mammals app, there is a static data source. However, in real life, you'll likely dynamically receive new data as the user interacts with the app. So, while in the Sea-mammals app, the data is sent all at once with didConnect, in real life, you'd set up a listener to call connection.send("newRow", params) as the dynamic data arrives.

note

In the above code, the withParams are just dictionary that contains the data that Sea-mammals sends over the wire to the desktop app.

Registering your plugin

Now all you need to do is let Flipper know about your new plugin. You do this by calling add on your FlipperClient, which is normally created at application startup:

let client = FlipperClient.shared()
// Add all sorts of other amazing plugins here ...
client?.add(SeaMammalsPlugin(MarineMammal.defaultList))
client?.start()

What next?

When starting your application, Flipper tells the desktop application about the plugin it supports (including 'Sea-mammals') and looks for a corresponding JavaScript plugin by the same name.

- + \ No newline at end of file diff --git a/docs/tutorial/javascript/index.html b/docs/tutorial/javascript/index.html index 84f9fd53ec4..e4214e3313b 100644 --- a/docs/tutorial/javascript/index.html +++ b/docs/tutorial/javascript/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Building a JavaScript (Browser) Plugin

caution

This tutorial requires a browser that supports WebSockets.

This section of the tutorial goes through the three steps required to build a JavaScript plugin.

Step 1 - install Flipper JavaScript SDK

Add the Flipper client to your web application. Run npm install js-flipper (yarn add js-flipper)

Step 2 - start the Flipper client

danger

Do not start the Flipper client in production! Preferably, do not even include Flipper in your production builds!

Use the following to start the Flipper client:

// We want to import and start flipper client only in development and test modes
let flipperClientPromise: Promise<FlipperClient> | undefined;
if (process.env.NODE_ENV !== 'production') {
flipperClientPromise = import('js-flipper').then(({flipperClient}) => {
flipperClient.start('React Tic-Tac-Toe');
return flipperClient;
});
}

Step 3 - call addPlugin to add your plugin

To register a new plugin with Flipper, call flipperClient.addPlugin and pass your plugin as an object.

Your plugin must conform to the following interface:

export interface FlipperPlugin {
/**
* @return The id of this plugin. This is the namespace which Flipper desktop plugins will call
* methods on to route them to your plugin. This should match the id specified in your React
* plugin.
*/
getId(): string;

/**
* Called when a connection has been established. The connection passed to this method is valid
* until {@link FlipperPlugin#onDisconnect()} is called.
*/
onConnect(connection: FlipperPluginConnection): void;

/**
* Called when the connection passed to `FlipperPlugin#onConnect(FlipperConnection)` is no
* longer valid. Do not try to use the connection in or after this method has been called.
*/
onDisconnect(): void;

/**
* Returns true if the plugin is meant to be run in background too, otherwise it returns false.
*/
runInBackground?(): boolean;
}

The onConnect and onDisconnect events, featured in the above snippet, are triggered every time the plugin becomes (in)active in the Flipper desktop application. If the plugin is a background plugin, these events are triggered typically only once (they might be triggered never if the Desktop user didn't enable the plugin, or multiple times if they enabled or disabled the plugin a few times).

The onConnect callback receive a connection which can be used to communicate with the backend:

// TicTacToe game status
const [status, setStatus] = useState('Waiting for Flipper Desktop Player...');
// TicTacToe game state
const [gameState, setGameState] = useState<GameState>({
cells: [],
turn: ' ',
winner: ' ',
});
// Flipper connection instance
const [connection, setConnection] = useState<FlipperPluginConnection>();

useEffect(() => {
flipperClientPromise?.then(flipperClient => {
flipperClient.addPlugin({
getId() {
// Name of the plugin
return 'ReactNativeTicTacToe';
},
onConnect(connection) {
// Once we connected, we display it to the user
setStatus('Desktop player present');
// And stash the connection object
setConnection(connection);

// We start listening to updates from Flipper Desktop
connection.receive('SetState', (gameState: GameState) => {
if (gameState.winner !== ' ') {
setStatus(
`Winner is ${gameState.winner}! Waiting for a new game...`,
);
} else {
setStatus(
gameState.turn === 'X'
? 'Your turn...'
: 'Awaiting desktop players turn...',
);
}
setGameState(gameState);
});

// We also request the initial state of the game from Flipper Desktop
connection.send('GetState');
},
onDisconnect() {
// When Flipper Desktop disconnects, we show it to the user
setConnection(undefined);
setStatus('Desktop player gone...');
},
});
});
}, []);

You might want to store the connection somewhere to be able to send more events as long as onDisconnect event hasn't been fired.

The connection object can also be used to listen to messages coming from the Desktop plugin. See Client Plugin API for details.

Live demo

An example plugin to play a little Tic-Tac-Toe between the Flipper Desktop and a React app can be found inside this repository as well (run yarn && yarn start in js/react-flipper-example to start the test project):

- + \ No newline at end of file diff --git a/docs/tutorial/js-custom/index.html b/docs/tutorial/js-custom/index.html index 9b10d468456..ef8165293da 100644 --- a/docs/tutorial/js-custom/index.html +++ b/docs/tutorial/js-custom/index.html @@ -17,7 +17,7 @@ - + @@ -29,7 +29,7 @@ Storing the state inside the closure ensures no state is mixed up.

Testing plugin logic

note

This section features a scenario where unit tests are always written before creating a Custom UI for a plugin.

Unit tests will be picked automatically by Jest if they are named like __tests__/*.spec.tsx, so create a file called __tests__/seamammals.spec.tsx and start the test runner by running yarn test --watch in your plugin root.

Here is the Initial Unit Test code:

// (1)
import {TestUtils} from 'flipper-plugin';
// (2)
import * as MammalsPlugin from '..';

test('It can store rows', () => {
// (3)
const {instance, sendEvent} = TestUtils.startPlugin(MammalsPlugin);

expect(instance.rows.get()).toEqual({});
expect(instance.selectedID.get()).toBeNull();

// (4)
sendEvent('newRow', {
id: 1,
title: 'Dolphin',
url: 'http://dolphin.png',
});
sendEvent('newRow', {
id: 2,
title: 'Turtle',
url: 'http://turtle.png',
});

// (5)
expect(instance.rows.get()).toMatchInlineSnapshot(`
Object {
"1": Object {
"id": 1,
"title": "Dolphin",
"url": "http://dolphin.png",
},
"2": Object {
"id": 2,
"title": "Turtle",
"url": "http://turtle.png",
},
}
`);
});
note

The code for the Initial Unit Test (shown above) contains numbered comments (such as '// (1)'), which are referenced in the following information.

Key points regarding the Initial Unit Test code:

  • Testing utilities for plugins are shipped as part of flipper-plugin, so can be imported directly (see (1)).
  • You directly import the above plugin implementation into your unit test.
  • By using as, you put the entire implementation into one object, which is the format in which your utilities expect them ((2)).
  • Using TestUtils.startPlugin ((3)) instantiates the plugin in a fully mocked environment where the plugin can do everything except for actually rendering, which makes this operationally inexpensive.
  • From the startPlugin, you get back an instance, which corresponds to the object returned from the plugin implementation (see (4) in the example Custom UI, above).
    • You also get a bunch of utilities to interact with the plugin. The full list is documented Desktop Plugin API page. However, for this test, the sole concern is with sendEvent.
  • By using sendEvent, you can mimic the client plugin sending events to your plugin (4). Similarly, you can emulate all other possible events, such as the initial connection setup with (.connect()), the user (de)selecting the plugin (.activate() / deactivate()), or a deeplink being triggered (.triggerDeepLink), and so on.
  • After the events have been sent, it's expected the internal state of the plugin should have been updated; this is asserted at point (5).
    • The assertions are provided by Jest. Particularly useful is toMatchInlineSnapshot, which generates the initial snapshot during the first run of the unit tests, saving a lot of effort.

Building a User Interface for the plugin

So far, in index.tsx, the Component hasn't yet done anything useful. This section explains how to build an effective and nice-looking UI.

Flipper leverages Ant design, so any official Ant component can be used in Flipper plugins.

The styling system used by Flipper can be found at the style guide, where the the different Layout elements are documented.

import React, {memo} from 'react';
import {Typography, Card} from 'antd';
import {
Layout,
PluginClient,
usePlugin,
createState,
useValue,
theme,
styled,
DataInspector,
DetailSidebar
} from 'flipper-plugin';

// (1)
export function Component() {
// (2)
const instance = usePlugin(plugin);
// (3)
const rows = useValue(instance.rows);
const selectedID = useValue(instance.selectedID);

// (4)
return (
<>
<Layout.ScrollContainer
vertical
style={{background: theme.backgroundWash}}>
<Layout.Horizontal gap pad style={{flexWrap: 'wrap'}}>
{Object.entries(rows).map(([id, row]) => (
<MammalCard
row={row}
onSelect={instance.setSelection}
selected={id === selectedID}
key={id}
/>
))}
</Layout.Horizontal>
</Layout.ScrollContainer>
<DetailSidebar>
{selectedID && renderSidebar(rows[selectedID])}
</DetailSidebar>
</>
);
}

function renderSidebar(row: Row) {
return (
<Layout.Container gap pad>
<Typography.Title level={4}>Extras</Typography.Title>
<DataInspector data={row} expandRoot={true} />
</Layout.Container>
);
}
note

The above User Interface code contains numbered comments (such as '// (1)') that are referenced in the following information.

Key points regarding the above User Interface code:

  • A plugin module can have many components but should always export one component named Component, which is used as the root component for the plugin rendering. The component mustn't take any props and will be mounted by Flipper when the user selects the plugin (see (1)).
  • Inside the component, you can grab the relevant instance of the plugin by using the usePlugin hook (see (2)). This returns the instance API returned in the Example Custom UI at the end of the plugin function. The original plugin definition is passed to the usePlugin as argument: this is done to get the typings of instance correct and should always be done.
  • With the useValue hook ((3)), you can grab the current value from the states created earlier using createState. The benefit of useValue(instance.rows) overusing rows.get(), is that the first will automatically subscribe your component to any future updates to the state, causing the component to re-render when new rows arrive.
  • Since both usePlugin and useValue are hooks, they usual React rules for them apply; they need to be called unconditionally. So, it's recommended to put them at the top of your component body. Both hooks can not only be used in the root Component, but also in any other component in your plugin component tree. So, it's not necessary to grab all the data at the root and pass it down using props. Using useValue as deep in the component tree as possible will benefit performance.
  • Finally, the data is rendered and returned (see (4)). The details have been left out here, as from this point it's just idiomatic React code.
rmation

The source of the other MammalCard component is located in GitHub.

note

It's recommended to keep components outside of the entry file as much as possible because components defined outside the index.tsx file will benefit from fast refresh.

Unit testing the User Interface

You can lower the chances of regression in the UI by adding another unit test to the seamammals.spec.tsx file and asserting that the rendering is correct and interactive. The following code provides an example:

test('It can have selection and render details', async () => {
// (1)
const {
instance,
renderer,
act,
sendEvent,
exportState,
} = TestUtils.renderPlugin(MammalsPlugin);

// (2)
sendEvent('newRow', {
id: 1,
title: 'Dolphin',
url: 'http://dolphin.png',
});
sendEvent('newRow', {
id: 2,
title: 'Turtle',
url: 'http://turtle.png',
});

// (3) Dolphin card should now be visible
expect(await renderer.findByTestId('Dolphin')).not.toBeNull();
// (4) Let's assert the structure of the Turtle card as well
expect(await renderer.findByTestId('Turtle')).toMatchInlineSnapshot(`
<div
class="css-ok7d66-View-FlexBox-FlexColumn"
data-testid="Turtle"
>
<div
class="css-vgz97s"
style="background-image: url(http://turtle.png);"
/>
<span
class="css-8j2gzl-Text"
>
Turtle
</span>
</div>
`);

// (5) Nothing selected, so we should not have a sidebar
expect(renderer.queryAllByText('Extras').length).toBe(0);

act(() => {
instance.setSelection(2);
});

// Sidebar should be visible now
expect(await renderer.findByText('Extras')).not.toBeNull();

// (6) Verify export
expect(exportState()).toEqual({
rows: {
'1': {
id: 1,
title: 'Dolphin',
url: 'http://dolphin.png',
},
'2': {
id: 2,
title: 'Turtle',
url: 'http://turtle.png',
},
},
selection: '2',
});
});
note

The above User Interface Unit Test code contains numbered comments (such as '// (1)') that are referenced in the following information.

As in the Initial Unit Test, you can use TestUtils to start your plugin. But rather than using startPlugin, you now use renderPlugin, which has the same functionality but also renders the component in memory, using the React Testing Library, this enables you to interact with DOM.

Key points regarding the above User Interface Unit Test code:

  • You start the UI test by sending some events to the plugin (see (2)). After which (see (3)), the new data should be reflected in the DOM.
  • Since you used <Card data-testid={row.title} in the component implementation (not shown above), you can search in the DOM based on that test-id to find the correct element. It is also possible to search for other entities, such as a specific classname. The available queries are documented in the React Testing Library.
  • Rather than just checking that the rendering isn't null, you can also take a snapshot of the DOM and assert that it doesn't change accidentally in the future. Jest's toMatchInlineSnapshot (see (4)) is quite useful for that. However, don't overuse it as large snapshots are pretty useless and just create a maintenance burden without catching much.
  • At point (5), the code simulates updating the selection from code and asserts that the sidebar has become visible. Note that the update is wrapped in act, which is recommended as it makes sure that updates are flushed to the DOM before you make queries and assertions on the DOM (the earlier sendEvent does apply act automatically and doesn't need wrapping)
    • Alternatively, you could have emulated actually clicking a DOM element, by using fireEvent.click(renderer.findByTestId('dolphin')) (for details, see Firing Events in the docs of the React Testing Library)
  • Finally (see (6)), the test grabs the final state of the plugin state by using the exportState utility. It returns all the persistable state of the plugin, based on the persist keys that were passed to createState in the Example Custom UI code.
  • You now assert that the plugin ends up in the desired state.
- + \ No newline at end of file diff --git a/docs/tutorial/js-publishing/index.html b/docs/tutorial/js-publishing/index.html index 355198f3e84..66025bce233 100644 --- a/docs/tutorial/js-publishing/index.html +++ b/docs/tutorial/js-publishing/index.html @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ By default Flipper plugins will be published to npmjs.com. Thus anyone can try install&use this plugin.

To keep plugin private, we can provide a .tar.gz archive with pluginc code and user can manually install the plugin. This is quite troublesome for most users and also does not provide a way to automatically update when new version is released.

Flipper provides a marketplace feature, which does help with discovery of plugins within organisation and also keeps the plugins private. To read more details on how marketplace works and how it can be integrated, please read this document: Marketplace.

- + \ No newline at end of file diff --git a/docs/tutorial/js-setup/index.html b/docs/tutorial/js-setup/index.html index 533b2de559a..6ebceed618b 100644 --- a/docs/tutorial/js-setup/index.html +++ b/docs/tutorial/js-setup/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Building a Desktop Plugin - Setup

Now that the native side is covered (see the previous pages in this tutorial), it's time to display the data you're sending on the desktop side.

The following desktop screenshot shows the 'Sea-mammals' plugin, of this tutorial, in action.

Custom cards UI for the sea mammals plugin

Scaffolding a new Desktop plugin

A new Flipper Desktop plugin can be scaffolded by running npx flipper-pkg init in the directory where you want to store the plugin sources.

danger

Don't run this command inside the Flipper repository!. Before running this command, make sure Flipper is closed.

The following snippet shows an example:

mkdir ~/FlipperPlugins
cd ~/FlipperPlugins
npx flipper-pkg init

Add the directory (shown in the snippet) to the Flipper watch folder if asked.

In this tutorial, you'll be creating a client plugin. device plugins are only used when creating plugins that don't connect to a specific application.

Take the following steps for npx:

  1. The npx tool asks you to provide the 'id' and 'title' for your plugin. For the sake of this tutorial, use 'sea-mammals' as 'id' and 'Sea Mammals' as the 'title'.
  2. The npx tool creates two files in the directory: package.json and src/index.tsx.
  3. After the process has finished, use yarn watch in the generated directory to start compiling the plugin on the fly.

Now that the package has been set up, you are ready to build a UI for your plugin. This can be achieved either by using a standardized table-based plugin, or by creating a custom UI.

For more background on the generated files and overall plugin structure, see the Plugin Structure page.

- + \ No newline at end of file diff --git a/docs/tutorial/js-table/index.html b/docs/tutorial/js-table/index.html index 0e877f7a18f..fdca37064d9 100644 --- a/docs/tutorial/js-table/index.html +++ b/docs/tutorial/js-table/index.html @@ -17,7 +17,7 @@ - + @@ -27,7 +27,7 @@

Building a Desktop Plugin - Showing a Table

Building a Table

One of the best ways to understand how your app works is to get access to its underlying data, which are used to display items on screen. An efficient way of achieving this is by showing the data in a table. The data for Sea-mammals has been optimized for easy display in a table, which you can sort, filter and select items for more detailed information.

Android Tutorial App

In this section of the tutorial, you'll be editing the index.tsx file that was generated in the previous scaffolding step.

Row Types

Start off by defining what your table rows look like:

type Row = {
id: number,
title: string,
url: string,
};
note

It's important that you have some unique identifier for every row so that you know when something new was added to the table; the 'id' field is sufficient

Columns

Define which columns to show and how to display them:

import {DataTableColumn} from 'flipper-plugin';

const columns: DataTableColumn<Row>[] = [
{
key: 'title',
width: 150,
},
{
key: 'url',
title: 'URL',
},
];

The keys used here will show up again in the next step when building your rows, so keep them consistent. The title defined for each column will show up as the header at the top of the table and will be default to the key value if omitted.

For the width, you can either choose a fixed number (pixels), a percentage, or leave it out to distribute the remaining available space.

Configuring the table

You now have a Row type that describes the data you'll be storing and a description of the columns to display. With these components it's a trivial task to get a table showing data, including search / sort and a details view, as shown in the following code snippet:

import {DataTableColumn, createTablePlugin} from 'flipper-plugin';

const {plugin, Component} = createTablePlugin<Row>({
columns,
method: 'newRow',
key: 'id',
});
export {plugin, Component};

The above code snippet has the following properties:

  • method - refers to the method that should be listened to to fill the table with data.
  • 'newRow' - refers back to the identifier used with connection.send to send the data to the Flipper desktop application in the previous step.
  • key - [optional] - but by setting this property, the 'id' field is used as an identifier. As a result, once a newRow message arrives with an existing id, it will overwrite the old entry, rather than appending a new one.

The createTablePlugin API supports more options, which are documented in the Dektop Plugin API page.

And that's all there is to it!

Starting Flipper will now compile your plugin and connect to the native side. It's a good idea to start Flipper from the command line to see any potential errors. The console in the DevTools is a great source of information if something doesn't work as expected, too.

The final result of this step can be seen at index_table.tsx.

What next?

You now have an interactive table that you can sort, filter and use to get additional information about the data you see on screen.

The createTablePlugin is a high-level abstraction that takes care of both connection management and a standardized UI for the most common scenario.

For many cases, this is all you need. However, sometimes you may want to build something a bit more custom, which is covered in the Desktop Plugin - Custom UI page

- + \ No newline at end of file diff --git a/docs/tutorial/marketplace/index.html b/docs/tutorial/marketplace/index.html index 549f9959c79..aac4596da3e 100644 --- a/docs/tutorial/marketplace/index.html +++ b/docs/tutorial/marketplace/index.html @@ -17,7 +17,7 @@ - + @@ -26,7 +26,7 @@

Marketplace

Flipper provides this feature to allow distribution of private plugins, which are not published to npmjs.com.

Plugin discovery

Plugins which are supported by currently connected app/device, will be listed under "Detected in app" section in sidebar. This significantly help users to discover useful plugins for their app.

Install plugins

Plugin auto-update

Once plugin is installed, it will also keep updating to latest version.

Enable Marketplace

Setup server

Instead of hosting plugins on npm server, we need to provide custom server, which will provide a list of internal plugins.

It is recommended to connect this server with internal package registry (to allow providing latest version).

Here is a example implementation of server using Verdaccio as package registry: marketplace-server-example.

Enable marketplace

Open Flipper settings and enter the URL to your Flipper marketplace server. It is also adviced to toggle ON the Enable auto update.

Install plugins
- + \ No newline at end of file diff --git a/docs/tutorial/react-native/index.html b/docs/tutorial/react-native/index.html index c34f67bc335..a736804f625 100644 --- a/docs/tutorial/react-native/index.html +++ b/docs/tutorial/react-native/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Building a React Native Plugin

caution

This section of the tutorial requires React Native 0.62 or higher.

Once you've connected Flipper to a React Native application, writing your own Flipper plugin can be done without reaching into the native world.

To expose Flipper to the JavaScript world, the React Native module react-native-flipper needs to be installed in the hosting application by running yarn add react-native-flipper and cd ios && pod install. If you are developing a plugin that is distributed as NPM package, make sure to add this to the installation instruction of your package as well!

Registering a new plugin is done by importing addPlugin from react-native-flipper and providing it an object that at least implements the method getId (the plugin id that should be used in the desktop plugin as well to make the connection) and two event handlers for the onConnect and onDisconnect events. The onConnect and onDisconnect events are triggered every time the plugin becomes (in)active in the Flipper desktop application. If the plugin is a background plugin, the events are triggered typically only once (they might be triggered never, if the Desktop user didn't enable the plugin, or multiple times if they enabled or disabled the plugin a few times).

The onConnect callback receive a connection which can be used to communicate with the backend:

import {addPlugin} from "react-native-flipper"

addPlugin({
getId() {
return 'ReactNativeExamplePlugin';
},
onConnect(connection) {
mammmals.forEach(({ title, pictureUrl }, index) => {
connection.send('newRow', {
id: index,
title,
url: pictureUrl
})
})
},
onDisconnect() {
}
})

You may want to store the connection somewhere to be able to send more events as long as onDisconnect event hasn't been fired.

The connection object can also be used to listen to messages coming from the Desktop plugin. See Client Plugin API for details.

An example plugin to play a little Tic-Tac-Toe between the Flipper Desktop and a React Native app can be found inside this repository as well (run yarn && yarn android in react-native/ReactNativeFlipperExample to start the test project):

- + \ No newline at end of file diff --git a/index.html b/index.html index 2681e3454e6..8c1b99f346a 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@

Extensible mobile app debugger

Flipper is a platform for debugging iOS, Android and React Native apps. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API.

Tools

Mobile development

Flipper aims to be your number one companion for mobile app development on iOS and Android. Therefore, we provide a bunch of useful tools including a log viewer, interactive layout inspector, and network inspector.

Learn more

Plugins

Extending Flipper

Flipper is built as a platform. In addition to using the tools already included, you can create your own plugins to visualize and debug data from your mobile apps. Flipper takes care of sending data back and forth, calling functions, and listening for events on the mobile app.

Learn more

Open Source

Contributing to Flipper

Both Flipper's desktop app and native mobile SDKs are open-source and MIT licensed. This enables you to see and understand how we are building plugins, and of course join the community and help improve Flipper. We are excited to see what you will build on this platform.

Explore the source on GitHub
- + \ No newline at end of file diff --git a/search/index.html b/search/index.html index f5c8c14fc3b..991e51ab8fa 100644 --- a/search/index.html +++ b/search/index.html @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - + \ No newline at end of file