{"id":16541404,"url":"https://github.com/thyrlian/screenshotsnanny","last_synced_at":"2025-03-16T19:32:12.568Z","repository":{"id":141703448,"uuid":"41375604","full_name":"thyrlian/ScreenshotsNanny","owner":"thyrlian","description":"Android library helps take screenshots for publishing on Google Play Store.","archived":false,"fork":false,"pushed_at":"2017-06-12T18:29:29.000Z","size":8388,"stargazers_count":235,"open_issues_count":0,"forks_count":29,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-02-27T12:44:42.987Z","etag":null,"topics":["android","automation","ci","screenshot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thyrlian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-08-25T16:45:00.000Z","updated_at":"2025-01-03T01:29:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"f8fc7b54-1abf-4a25-abf9-c35779011b20","html_url":"https://github.com/thyrlian/ScreenshotsNanny","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thyrlian%2FScreenshotsNanny","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thyrlian%2FScreenshotsNanny/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thyrlian%2FScreenshotsNanny/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thyrlian%2FScreenshotsNanny/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thyrlian","download_url":"https://codeload.github.com/thyrlian/ScreenshotsNanny/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243826797,"owners_count":20354221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","automation","ci","screenshot"],"created_at":"2024-10-11T18:54:55.341Z","updated_at":"2025-03-16T19:32:12.563Z","avatar_url":"https://github.com/thyrlian.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![API](https://img.shields.io/badge/API-14%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=14)\n[![JCenter](https://api.bintray.com/packages/thyrlian/android-libraries/com.basgeekball.screenshotsnanny/images/download.svg) ](https://bintray.com/thyrlian/android-libraries/com.basgeekball.screenshotsnanny/_latestVersion)\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-ScreenshotsNanny-green.svg?style=true)](https://android-arsenal.com/details/1/2825)\n[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-%23181-brightgreen.svg)](http://androidweekly.net/issues/issue-181)\n\n# ScreenshotsNanny\n\n## Introduction\n\nUntil the time of writing, the Android toolchain doesn't have anything to help take screenshots automatically for publishing on Google Play Store.\n\nThe other fact is that most of the modern apps consume internet resources, or have UGC (user-generated content).  And for the screenshots showing on Google Play, no one would like to see any arbitrary content which may be ugly or even inappropriate.\n\nBe professional!  Be beautiful!  You can achieve it easily by using ScreenshotsNanny.\n\n## Comparison\n\nBelow are two different screenshots for the same activity.  The left one is using real arbitrary content, while the right one is using prepared mock response.\n\n![Comparison](https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_comparison.png)\n\n## Setup \u0026 Sample code\n\nThere are two approaches to utilizing this library.\n\n* Setup an **automated UI test** (e.g. [Espresso](https://google.github.io/android-testing-support-library/docs/espresso/index.html)).\n* Create another **product flavor** in your project to do the screenshot job.\n\nI'll explain the product flavor approach in detail.  You can also check out the demo module along with this project.\n\n**1** - Add a product flavor (let's name it \"**screenshots**\") to your target module's **build.gradle**:\n```groovy\nproductFlavors {\n    prod {\n        applicationId \"PRODUCT_DEFAULT_APP_ID\"\n    }\n    screenshots {\n        applicationId \"PACKAGE_NAME.screenshots\"\n    }\n}\n```\n**2** - Create a blank dummy activity in the screenshots flavor: *MODULE/src/screenshots/java/PACKAGE_NAME/ScreenshotsPrimeActivity.java*\n\nYou can leave the layout as it is (an empty view group), because we don't really need it.\n\n**3** - Set the created activity as the launcher activity in that product flavor:\n\n*MODULE/src/screenshots/AndroidManifest.xml*\n```xml\n\u003cmanifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n    \u003capplication\u003e\n        \u003cactivity android:name=\"PACKAGE_NAME.ScreenshotsPrimeActivity\"\n            android:label=\"@string/title_activity_screenshots_prime\"\u003e\n            \u003cintent-filter\u003e\n                \u003caction android:name=\"android.intent.action.MAIN\" /\u003e\n                \u003ccategory android:name=\"android.intent.category.LAUNCHER\" /\u003e\n                \u003ccategory android:name=\"android.intent.category.DEFAULT\" /\u003e\n            \u003c/intent-filter\u003e\n        \u003c/activity\u003e\n    \u003c/application\u003e\n\u003c/manifest\u003e\n```\n\n**4** - Add the core screenshot code to the new launcher activity **ScreenshotsPrimeActivity.java**.\n\nThere are two major methods: ```startActivityAndTakeScreenshot``` \u0026 ```startActivityContainsMapAndTakeScreenshot```.  Without passing **screenshotDelay**, the former one uses default value 3 seconds, and the latter one takes screenshot immediately when map view is ready.  You could give your own screenshotDelay to both of the methods.\n```java\npublic class ScreenshotsPrimeActivity extends AppCompatActivity {\n\n    private MockServerWrapper mServer;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_screenshots_prime);\n\n        // Set up the screenshot fixture\n\n        // Set language (resources configuration) other than the default one if it's necessary\n        // LanguageSwitcher.change(this, \"de\");\n\n        // Set up the mock server\n        mServer = new MockServerWrapper();\n        // Read mock response(s) from resource directory\n        String response = ResourceReader.readFromRawResource(ScreenshotsPrimeActivity.this, R.raw.github_user);\n        ParameterizedCallback changeUrlCallback = new ParameterizedCallback() {\n            @Override\n            public void execute(String value) {\n                // The MockServer runs on arbitrary port each time\n                // We have to change production's base URL to the MockServer URL via reflection\n                PowerChanger.changeFinalString(GithubService.class, \"API_URL\", value);\n            }\n        };\n        // Start mock server with canned response(s), it accepts response(s) as varargs\n        mServer.start(changeUrlCallback, response);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n        // Start desired activities one by one, and take screenshot accordingly\n\n        startActivityAndTakeScreenshot(MainActivity.class, new Callback() {\n            @Override\n            public void execute() {\n                // Start a normal activity\n                startActivity(new Intent(ScreenshotsPrimeActivity.this, MainActivity.class));\n            }\n        });\n\n        startActivityAndTakeScreenshot(SecondActivity.class, new Callback() {\n            @Override\n            public void execute() {\n                // Start an activity by an intent which contains something\n                startActivity(SecondActivity.createIntent(ScreenshotsPrimeActivity.this, \"London bridge is falling down\"));\n            }\n        });\n\n        startActivityAndTakeScreenshot(NetworkActivity.class, new Callback() {\n            @Override\n            public void execute() {\n                // This activity will consume mock response and present it\n                startActivity(new Intent(ScreenshotsPrimeActivity.this, NetworkActivity.class));\n            }\n        });\n\n        startActivityAndTakeScreenshot(AccountActivity.class, new Callback() {\n            @Override\n            public void execute() {\n                // Prepare persistent data before starting the activity\n                AccountManager.create(getApplicationContext(), \"Bruce Lee\");\n                AccountManager.update(getApplicationContext(), 1048576);\n                startActivity(new Intent(ScreenshotsPrimeActivity.this, AccountActivity.class));\n            }\n        });\n\n        startActivityContainsMapAndTakeScreenshot(MapsActivity.class, new Callback() {\n            @Override\n            public void execute() {\n                // Take screenshot for an activity which contains Map, need to pass map view id\n                startActivity(new Intent(ScreenshotsPrimeActivity.this, MapsActivity.class));\n            }\n        }, R.id.map);\n\n        if (!ActivityCounter.isAnyActivityRunning) {\n            Log.i(Constants.LOG_TAG, \"⚙ Done.\");\n            // Stop mock server when all screenshot jobs are done\n            mServer.stop();\n            finish();\n        }\n    }\n}\n```\n\n**5** - Select **screenshotsDebug** as build variant, run it.  Then all screenshots will be placed under *DEVICE_STORAGE/Screenshots/APP_NAME/*.  Each screenshot file is named as corresponding activity name (format is PNG).\n\n## Screenshots\n\n* The Android Status Bar won't be captured as part of the screenshot, so you don't have to worry about the messy icons there.\n* Forget about the annoying on-screen keyboard, this library will hide it for you.\n* MapView (no matter if it fulfills the window or not) will also be taken into the screenshot.\n* For any activity consumes network resources, you can replace the content by canned mock responses.  (This library is using [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver) from [Square, Inc.](https://github.com/square))\n* Some activities may read values from persistent data (e.g. SharedPreferences), you can also prepare the values before activity starts.\n\n\u003ca href=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_main_activity.png\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_main_activity.png\" height=\"300\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_second_activity.png\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_second_activity.png\" height=\"300\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_network_activity.png\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_network_activity.png\" height=\"300\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_account_activity.png\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_account_activity.png\" height=\"300\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_maps_activity.png\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/screenshot_maps_activity.png\" height=\"300\"\u003e\u003c/a\u003e\n\n(sorry, these demo activities layouts were made with poor design, look ugly)\n\n## Logs\n\nFilter: tag = SSN\n\n![Logs](https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/logs.png)\n\n## Import as dependency\n\nGradle: (available in Bintray's [JCenter](https://bintray.com/thyrlian/android-libraries/com.basgeekball.screenshotsnanny/view))\n```java\ndependencies {\n    compile 'com.basgeekball:screenshots-nanny:1.2'\n}\n```\n\n## Publish\n\n```bash\ngradle generateRelease\n```\n\n## License\n\nCopyright (c) 2015 Jing Li. See the LICENSE file for license rights and limitations (MIT).\n\n## Last but not least\n\nThis is made in Berlin with love and passion  ʕ´•ᴥ•`ʔ\n\n\u003ca href=\"../../\" target=\"_blank\"\u003e\u003cimg src=\"https://github.com/thyrlian/ScreenshotsNanny/blob/master/resource/images/made_in_berlin_badge.png\" height=\"200\"\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthyrlian%2Fscreenshotsnanny","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthyrlian%2Fscreenshotsnanny","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthyrlian%2Fscreenshotsnanny/lists"}