EDIT MODE

モバイルアプリ開発に関することを書いています

モバイルアプリデバッガーFlipperの使用感

Androidのネイティブアプリで使ってみたら、思ったより使い心地良かったので備忘録的に使用感を書いていきます。

Flipperとは

iOSもしくはAndroidアプリをデバッグするためのプラットフォームです。シンプルなデスクトップアプリとして提供されていて、Mac/Linux/Windowsで利用することが出来ます。Flipperの機能はプラグイン形式で提供されているので、自作し拡張することも可能です。

また、ソースコードはOSSとして公開されていて、コントリビュートすることも可能です。

fbflipper.com

Stethoとの違い

facebook発のAndroid用デバッグツールとしては、Stethoが有名だと思います。主にネットワークやデータベースなどの中身を見るために、重宝されているかと思います。

FlipperとStethoの主な違いは、こんな感じです。

  Flipper Stetho
クライアント デスクトップアプリ Chrome DevTool
対応OS Android/iOS Android
機能拡張 ×

facebook.github.io

Flipperデスクトップアプリの使い方

クライアントアプリを起動した後で、実機もしくはエミュレーター/シミュレーターを接続すると認識し、Flipper SDKが組み込まれたアプリが起動されると、下図のようにアタッチされます。

左側のペインにアプリに組み込まれた機能が並び、クリックすると右側のペインに対応するUIが表示されるというシンプルなクライアントアプリです。(※下図はMac版デスクトップアプリです。)

キャプチャ機能があるのが地味に嬉しいですね。

f:id:androhi:20191127200103p:plain
Flipperクライアントアプリ

公式提供のプラグイン

2019年11月現在、公式が提供しているプラグインは全部で9個あります。(※対応OSは2019年11月時点のものです。)

Plugin 機能 Android iOS
Layout Viewの階層やプロパティをを解析してくれるお馴染みの機能です。
もちろん値を上書きすることも出来ます。
LithoやComponentKitにも対応したり、結構多機能。
Navigation ディープリンクに関するデバッグを楽にしてくれるます。
ディープリンクのログを自動記録したりアプリに送ったり出来ます。
×
Network 通信の内容をダンプしてくれるお馴染みの機能です。
AndroidではOkHttp用のInterceptorクラスも用意されていて実装が簡単です。
Database データベースの読み書きが出来ます。
クエリも発行出来ます。
Images ネットワーク上の画像に関するデバッグを楽にしてくれます。
ただしこのプラグインでサポートしている画像ローダーは、Frescoのみです。
×
Sandbox クライアントアプリからモバイルアプリに値を送るため仕組みを提供してくれます。
(イメージとしては簡易的なRemoteConfigみたいな感じかなと思います。)
×
SharedPreference モバイルアプリのPreferenceファイルの読み書きが出来ます。
LeakCanary メモリーリークを検知してくれるお馴染みのツールが、Flipperにも対応してくれています。 ×
CrashReporter モバイルアプリがクラッシュしたことを検知して、通知してくれます。 ×

実装方法

公式サイトに詳しく載っていますので、その通り進めれば問題なかったです。またiOS/Androidともにサンプルコードもしっかりあるので、不明点があればそちらも見た方がいいと思います。

fbflipper.com

Android向けTips

OkHttpのNetworkInspectorにFlipperを使いたいとき、OkHttpClientをDIで注入する方法がドキュメントに無かったのですが、以下のようにやるとうまくいきました。(以下はDaggerを使った場合の例です。)

    @Singleton
    @Provides
    fun provideHttpClient(context: Context): OkHttpClient {

        // ApplicationクラスでセットしたNetworkPluginのインスタンスを取得
        val plugin = AndroidFlipperClient.getInstance(context).getPlugin(NetworkFlipperPlugin.ID)

        return OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .addNetworkInterceptor(FlipperOkhttpInterceptor(plugin))
            .build()
    }

各プラグインはIDを持っているので、そこからプラグインのインスタンスを取得出来るようです。

まとめ

FlipperではStethoでやや不便だなと感じていた、Chrome DevToolを使う点やiOS未対応な点(まだ未対応のプラグインが多いですが...)などが解消されていて、とても使いやすいデバッグツールでした。

開発元が「Stethoは引き続き提供する」と言っていますが、個人的にはすぐにでも乗り換えてしまってもいいなと感じました。(僕はたぶんプロダクトに投入すると思います。)

AndroidXでのバックキー制御

AndroidX以前

今までバックキーの制御と言えば、例えば以下のように Activity#onBackPressed() でゴニョゴニョしていました。

    override fun onBackPressed() {
        // バックキーイベントでFragmentで何かしたいとき
        val fragment = supportFragmentManager.findFragmentByTag("HogeFragment")
        fragment?.something()

        // スタックに積んだFragmentを1個前に戻したいとき
        if (supportFragmentManager.backStackEntryCount > 0) {
            supportFragmentManager.popBackStack()
        } else {
            super.onBackPressed()
        }

BackStackとかはまだいいですけど、Fragment側の処理をcallしたいときなどはActivityに特定のFragmentの依存が入ってしまって、残念な気持ちになっていました。

AndroidX以降

AndroidXで提供されているActivityには、Version 1.0.0-alpha01で Activity#addOnBackPressedCallback () というメソッドが追加され、 OnBackPressedCallback インターフェース経由でバックキーのイベントを受けられるようになってました。

つまりFragment内にバックキー制御のロジックを閉じ込めることが出来るということです。控えめに言って最高です。

developer.android.com

こんな感じで書くことが出来ました。

...
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
...

class MyFragment : Fragment() {

    private val callback = object : OnBackPressedCallback {
        override fun handleOnBackPressed(): Boolean {
            return if (isHoge) {
                something()
                true
            } else {
                false
            }
        }
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        activity?.addOnBackPressedCallback(callback)
        ...
    }

    override fun onDestroy() {
        super.onDestroy()
        activity?.removeOnBackPressedCallback(callback)
    }

これだけでも嬉しいですが、リファレンスを見るとArchitecture ComponentのLifecycleにも対応していました。

developer.android.com

つまり先程のサンプルコードは、以下のように書けます。

...
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
...

class MyFragment : Fragment() {

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        activity?.addOnBackPressedCallback(this, object : OnBackPressedCallback {
            override fun handleOnBackPressed(): Boolean {
                return if (isHoge) {
                    something()
                    true
                } else {
                    false
                }
            }
        })
        ...
    }

これでaddしたcallbackの開放を気にする必要が無くなりました。コード量も減るし、読みやすいです。 (OnBackPressedCallbackはJavaで実装されているのでSAM変換も可能ですし、実際にはさらにコード量は減らせます。)

これを発見したときにAndroidXに移行して良かったなと、心の底から思いました。AndroidX最高です。

MockKを使ったViewModelのテスト

概要

以下のような、シンプルなViewModelのテストをMockitoを使って書いたのですが、これをMockKで書き換えることにしました。

Mockitoを使ったViewModelのテスト

@RunWith(RobolectrictTestRunner::class)
class UserViewModelTest {

    @Mock private lateinit var mockRepository: MyRepository
    @Mock private lateinit var observer: Observer<List<UserPoint>>
    
    private lateinit var viewModel
    
    @Before
    fun init() {
        val immediate = object : Scheduler() {
            override fun createWorker(): Scheduler.Worker {
                return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
            }
        }
        RxJavaPlugins.setInitIoSchedulerHandler { _ -> immediate }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> immediate }

        MockitoAnnotations.initMocks(this)
    }
    
    @Test
    fun someTest() {
        val userList = listOf()
        `when`(mockRepository.findUserList()).thenReturn(Observable.just(userList))

        viewModel = UserViewModel(repository)
        viewModel.users.observeForever(observer)
        viewModel.loadUserList()

        verify(mockRepository, only()).findUserList()
        verify(observer).onChanged(userList)
    }
}

MockKとは

MockKはKotlinのためのMocking Libraryです。 ドキュメントも充実しているし、KotlintだとMockitoで困ることがほぼ解決出来ます。

便利なポイントはドキュメントにも列挙されています。個人的にはObjectのMockがシュッと書けて好きです。

mockk.io

MockKでViewModelのテスト

前述のコードをMockKで書き直すと以下のようになりました。

MockKを使ったViewModelのテスト

@RunWith(RobolectricTestRunner::class)
class UserViewModelTest {

    @MockK
    private lateinit var mockRepository: MyRepository
    @MockK
    private lateinit var observer: Observer<List<User>>

    private lateinit var viewModel

    @Before
    fun setup() {
        val immediate = object : Scheduler() {
            override fun createWorker(): Scheduler.Worker {
                return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
            }
        }
        RxJavaPlugins.setInitIoSchedulerHandler { _ -> immediate }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> immediate }

        MockKAnnotations.init(this)
    }

    @Test
    fun someTest() {
        val userList = listOf()
        every { mockRepository.findUserList() } returns Observable.just(userList)

        viewModel = UserViewModel(mockRepository)
        viewModel.users.observeForever(observer)
        viewModel.loadUserList()

        verify { mockRepository.findUserList(authToken) }
        assert(viewModel.users.value == userList)
    }
}

ところが、これを実行するとMockKのイニシャライズが失敗し、以下のようなエラーが発生してしまいました。

java.lang.NoClassDefFoundError: io/mockk/proxy/jvm/dispatcher/JvmMockKWeakMap

    at io.mockk.proxy.jvm.JvmMockKAgentFactory$init$Initializer.handlerMap(JvmMockKAgentFactory.kt:98)
    at io.mockk.proxy.jvm.JvmMockKAgentFactory$init$Initializer.init(JvmMockKAgentFactory.kt:43)
    at io.mockk.proxy.jvm.JvmMockKAgentFactory.init(JvmMockKAgentFactory.kt:102)
    at io.mockk.impl.JvmMockKGateway.<init>(JvmMockKGateway.kt:45)
    at io.mockk.impl.JvmMockKGateway.<clinit>(JvmMockKGateway.kt:163)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
        ...
Caused by: java.lang.ClassNotFoundException: io.mockk.proxy.jvm.dispatcher.JvmMockKWeakMap
    at org.robolectric.internal.bytecode.InstrumentingClassLoader.getByteCode(InstrumentingClassLoader.java:168)
    at org.robolectric.internal.bytecode.InstrumentingClassLoader.findClass(InstrumentingClassLoader.java:123)
    at org.robolectric.internal.bytecode.InstrumentingClassLoader.loadClass(InstrumentingClassLoader.java:95)
    ... 34 more

何が原因か分からなかったのですが、そもそもRobolectrictTestRunnerを使っている理由は、テストにLiveDataが含まれておりvalueの更新処理をMockしてもらうためです。

調べてみると、下記記事のようにLiveDataのユニットテストは core-testing ライブラリを使うのが正攻法のようなので、Robolectrictと置き換えてみました。

medium.com

MockK&core-testingを使ったViewModelのテスト

class UserViewModelTest {

    @Rule
    @JvmField
    var rule = InstantTaskExecutorRule()

    @MockK
    private lateinit var mockRepository: MyRepository
    @MockK
    private lateinit var observer: Observer<List<User>>

    private lateinit var viewModel

    @Before
    fun setup() {
        val immediate = object : Scheduler() {
            override fun createWorker(): Scheduler.Worker {
                return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
            }
        }
        RxJavaPlugins.setInitIoSchedulerHandler { _ -> immediate }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> immediate }

        MockKAnnotations.init(this)
    }

    @Test
    fun someTest() {
        val userList = listOf()
        every { mockRepository.findUserList() } returns Observable.just(userList)

        viewModel = UserViewModel(mockRepository)
        viewModel.users.observeForever(observer)
        viewModel.loadUserList()

        verify { mockRepository.findUserList(authToken) }
        assert(viewModel.users.value == userList)
    }
}

変更したのは、 RobolectrictTestRunner を外しRuleに InstantTaskExecutorRule を設定したことです。 これでやっとうまくテストをパス出来ました。

まとめ

  • LiveDataのユニットテストは core-testing を使う
  • 今の所MockK(v1.8.9)とRobolectrict(v3.0.0)の組み合わせはうまくいかない

Android Architecture Components 勉強会#1 に参加してきました

勉強会の参加レポート

GDG東京が主催のAndroid Architecture Components 勉強会の第1回目に参加してきました。 素晴らしい勉強会だったので記事にしたかったのと、学んだことの復習のために書いてます。

ちなみにこの勉強会は全4回の予定だそうで、Lifecycles(今回) -> LiveData -> ViewModel -> Roomと続いていくそうです。

gdg-tokyo.connpass.com

第1回目の講師は「あんざい」さんで、この方は本当に発表が上手いのですが今回もとても聞きやすくて、最後まで1ミリも飽きませんでした。

前半はLifecyclesの説明で、後半はあんざいさんが用意してくださった課題をみんなで取り組みました。課題にはいくつかの問いが用意されていて、動作確認後に参加者が答える形式だったので、楽しく課題に取り組めました。

課題も1個が丁度いいボリュームで、1時間くらいしかなかったにも関わらず、全部で5個くらいやったと思います。

以下のスライドに課題も載ってますが、現地でみんなとやるのが楽しかったので、もしお時間と場所的に可能であれば第2回目に参加することをおすすめします。

Lifecyclesの復習

1. 基本

必要な設定

プロジェクトのbuild.gradleにgoogleリポジトリ(Android Gradle Plugin 3.0ならデフォルトで記述有り)を追加して、モジュールのbuild.gradleにsupport-v4もしくはappcompat-v7を入れる。理由は、v26.1.0からsupport-v4のFragmentActivityやFragmentが、Lifecycleを組み込んでいるため。

メインクラス

  • Lifecycle
    • ライフサイクル状態の取得やObserverの追加・削除を行う

Enum

  • Lifecycle.State
    • ライフサイクル状態を表すenum
  • Lifecycle.Event
    • ライフサイクルのイベントを表すenum

Interface

  • LifecycleOwner
    • Lifecycleを持つクラスを表すためのsingle method interface
  • LifecycleObserver
    • Lifecycleに追加・削除できるObserverを表すinterface
    • @OnLifecycleEvent アノテーションをつけて自分でイベントメソッドを作る
    • Lifecycle.Event.ON_ANYは全てのイベントで呼ばれる
    • 第2引数でEventを受け取れる
  • GenericLifecycleObserver
    • あんざいさんが資料作成中にドキュメントから姿を消した幻のinterface
    • 便利なんだけど今後は使用しないほうがいいかも

その他

  • LifecycleRegistry
    • 複数のObserverを扱えるLifecycleの実装

2. 拡張機能

必要な設定

モジュールのbuild.gradleに以下を追記するだけ

dependencies {
    implementation 'android.arch.lifecycle:extensions:1.0.0'
}

便利クラス

  • LifecycleService
    • LifecycleOwnerが実装されたService
  • ServiceLifecycleDispatcher
    • ServiceにLifecycle機能をもたせるためのヘルパークラス
  • ProcessLifecycleOwner
    • アプリのプロセス全体に対するLifecycleを提供
    • こいつでアプリのForeground/Background判定が簡単に出来る
    • サポートライブラリでないFragmentでも検知可能

3. 注意点

onSaveInstanceState() まわりは要注意。 SupportLibraryのFragmentでは、onSaveInstanceState()が呼ばれると状態は CREATED になるようだ。

詳細は以下を読むほうがいい。

https://developer.android.com/topic/libraries/architecture/lifecycle.html#onStop-and-savedState

4. 基本的なサンプルコード

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val TAG_NAME = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // create observer
        val observer = object: LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_START)
            fun calledWhenOnStart(source: LifecycleOwner) {
                Log.i(TAG_NAME, "ON_START: " + source.lifecycle.currentState.name)
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
            fun calledWhenOnAny(source: LifecycleOwner, event: Lifecycle.Event) {
                Log.i(TAG_NAME, "ON_ANY: " + source.lifecycle.currentState.name + " arg2: " + event.name)
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
            fun calledWhenOnDestroy(source: LifecycleOwner) {
                Log.i(TAG_NAME, "ON_DESTROY: " + source.lifecycle.currentState.name)
                // onDestroy()でやるより、ここでremoveObserverするのが楽
                source.lifecycle.removeObserver(this)
            }
        }
        // 何回addしても呼ばれるのは1回
        lifecycle.addObserver(observer)
    }

Android SDKにバンドルされてるIntentアクションのリスト

知ってる人は知ってることなんでしょうけど、自分的には大発見だったので嬉しくて記事にしました。

ことの発端は、OSが投げてくるBroadcastの一覧を探してたところ、BroadcastのAPIガイドを呼んでいたら次のような一文を見つけたためです。

For a complete list of system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the Android SDK.

引用:Broadcasts | Android Developers

慌ててfindコマンド叩いたら本当にありました。全然知らなかった。

$ find . -name "broadcast_actions.txt"
./platforms/android-24/data/broadcast_actions.txt
./platforms/android-25/data/broadcast_actions.txt
./platforms/android-27/data/broadcast_actions.txt
./platforms/android-21/data/broadcast_actions.txt
./platforms/android-26/data/broadcast_actions.txt
./platforms/android-19/data/broadcast_actions.txt

なるほど、APIレベルごとにあるんですね。

Android SDKにバンドルされているリスト

broadcast_actions以外にも一覧として欲しい情報が、いろいろとありました。いづれも sdk/platforms/android-[API level]/data の中にあります。

$ ls
NOTICE.txt            annotations.zip       broadcast_actions.txt features.txt          layoutlib.jar         service_actions.txt   widgets.txt
activity_actions.txt  api-versions.xml      categories.txt        fonts                 res                   tzdata
  • broadcast_actions.txt
    • OSがブロードキャストとして通知するアクションの一覧
  • features.txt
    • AndroidManifestに宣言できる機能の一覧
  • service_actions.txt
    • 各サービスを利用する際に指定するアクションの一覧(?)
  • widgets.txt
    • Viewコンポーネントの一覧(?)
  • activity_actions.txt
    • startActivity()とかで使えるアクションの一覧
  • categories.txt
    • 単なるカテゴリの一覧(?)

※ ?のものは推測です。もしかしたら別の意図のある一覧かもしれません。

まとめ

  • Android SDKには各APIレベル毎にIntentアクションなどのリストがある
  • 各リストはフルネームが列挙されてるので詳細はリファレンスで調べる必要がある

2段階認証を有効にしてるGitHubのHttps認証をMacのkeychainに登録する方法

GitHubのリモートリポジトリを、Httpsでコマンドラインから快適にアクセスするのに、2つ超えなければいけない壁があるのですが、PC買い換える度に毎回ググってるのでまとめました。 ここにまとめた内容は2段階認証を有効にしている場合です。

やること

  1. osxkeychain helper をインストールしてGit Configに設定する
  2. Personal Access Tokenを発行してkeychainに登録する

1. osxkeychain helper をインストールしてGit Configに設定する

1-1. oxkeychain helperがインストールされているか確認する。

以下のように表示されればインストール済み。そうでなければHomebrewで最新のgitをインストールする。

$ git credential-osxkeychain
Usage: git credential-osxkeychain <get|store|erase>

1-2. Gitのconfigにosxkeychain helperを使うことを登録する。

$ git config --global credential.helper osxkeychain

参考:Caching your GitHub password in Git - User Documentation

2. Personal Access Tokenを発行してkeychainに登録する

2-1. Personal Access Tokenを発行する。

Settings -> Developer Settings -> Personal access tokens -> Generate new token を選択する。

Permissionを選択する必要があるので、 repo を選択する。(delete repoもあってもいいかもしれない。)

※ tokenは作成直後しか見られないので、一時的に何かにメモっておいたほうがいい。

2-2. tokenを入力しkeychainに登録する。

リモートリポジトリにアクセスすると、プロンプトでuser nameとpasswordを聞かれるので、passwordに発行したaccess tokenを入力する。 一度入力すれば、自動的にkeychainに登録され次回からは入力を省略できる。

$ git clone https://github.com/username/repo.git
Username: [user name]
Password: [personal access token]

2-3. keychainに登録されるとgithub.comが見える。

f:id:androhi:20171212235049p:plain

参考:Creating a personal access token for the command line - User Documentation

Support Library の 2016 年の issue を振り返る

この記事は「 Qiita Advent Calendar 2016 Android その2 」の 8 日目の記事です。

Android 関連のプロダクトでオープンになっているものは、Issue Tracker で不具合報告や要望などが管理されているのは、ご存知かと思います。 今回はその中で、2016 年に SupportLibrary として issue が切られているものを、振り返ってみたいと思います。なお、調査したのは 2016/12/7 時点の内容です。

対象とした issue

  • Component が Support-Libraries のもの
  • 作成日が 2016/1/1 以降のもの
  • Status は全て対象
  • 上記を満たす issue は 1081 件

Stars Top 3

No.1 : 211 stars

  • ID : 202658
  • Summary : "New support library: support-sqlite. Allow developers to use customized sqlite with existing Android Java bindings"
  • Status : Assigned

サポート・ライブラリに SQLite を扱うためのものを、作ってみてはどうかという issue が最も多くのスターを集めていました。 背景として、Android の OS バージョンによって SQLite のバージョンがバラバラなので、開発者が SQLite をコントロールできないのは不便すぎるからということです。既に、3rd-party のもので存在するようですが、オフィシャルにサポートしてくれると嬉しいので、ぜひ実現して欲しいですね。

No.2 : 153 stars

  • ID : 210615
  • Summary : "Databinding with Jack compiler"
  • Status : Assinged

Jack Compiler で Databinding をビルドできるようにして欲しいという要望のようです。 中の人は Databinding だけ特別扱いできるものではないから、もうちょっと待ってくれとのことです。コメントを見ると徐々にゴールに向かってる感はあるっぽいですね。

No.3 : 109 stars

  • ID : 220250
  • Summary : "FAB can no longer be anchored to indirect children of CoordinatorLayout"
  • Status : Released

スクロールによって連動するようレイアウトしたはずの FAB が、期待通りに動かないという不具合報告のようです。 ver24.2.0 で issue が作成され、ver24.2.1 で修正がリリースされたとして Released になっていますが、その後も同様の現象が起きるという報告がコメントに書かれていました。 CoodinatorLayout と Anchor の組み合わせは、基本形はいいのですがいろんな要素が影響し合うとややこしいですし、使い方の問題なのか不具合なのか難しいですね。

Priority High & Critical

  • Databinding : 9 件
  • ConstraintLayout : 5 件
  • Other : 5 件

プライオリティが、 High もしくは Critical に設定されているものの内訳を、カウントしてみました。ほぼほぼ Databinding と ConstraintLayout に関するものでした。それだけ、どちらも注目度が高いということですね。

最後に

本当は、もっといろいろまとめたかったのですが、時間が無くこれしか書けませんでした。 後日改めて、ちょっとずつ書き足していきたいと思います。

最後に issue tracker からエクスポートした CSV を整理したものを貼って、記事を終えたいと思います。 https://docs.google.com/spreadsheets/d/1pG5e5GVxJmjuF6Y5RCBdsCPiQ8WKSBWZjEBoskUabrQ/edit?usp=sharing