{"id":37020569,"url":"https://github.com/andviane/google-books-android-viewer","last_synced_at":"2026-01-14T02:24:02.549Z","repository":{"id":57732408,"uuid":"91984043","full_name":"andviane/google-books-android-viewer","owner":"andviane","description":"Android library to bridge between RecyclerView and sources like web page or database. Includes demonstrator (Google Books viewer)","archived":false,"fork":false,"pushed_at":"2020-09-23T16:36:26.000Z","size":7982,"stargazers_count":41,"open_issues_count":5,"forks_count":14,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-03-15T08:03:46.014Z","etag":null,"topics":["adapter","android","async","chunk","first-timers-only","infinite-scroll","library","listview","recyclerview","ui-components"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andviane.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-21T20:04:43.000Z","updated_at":"2024-03-15T08:03:46.015Z","dependencies_parsed_at":"2022-09-10T19:52:08.392Z","dependency_job_id":null,"html_url":"https://github.com/andviane/google-books-android-viewer","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/andviane/google-books-android-viewer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andviane%2Fgoogle-books-android-viewer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andviane%2Fgoogle-books-android-viewer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andviane%2Fgoogle-books-android-viewer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andviane%2Fgoogle-books-android-viewer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andviane","download_url":"https://codeload.github.com/andviane/google-books-android-viewer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andviane%2Fgoogle-books-android-viewer/sbom","scorecard":{"id":194896,"data":{"date":"2025-08-11","repo":{"name":"github.com/andviane/google-books-android-viewer","commit":"dd63a48ed3a5c36823c007b8d8d97ec6f6b0ebda"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":3,"reason":"Found 6/17 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T21:38:25.570Z","repository_id":57732408,"created_at":"2025-08-16T21:38:25.570Z","updated_at":"2025-08-16T21:38:25.570Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["adapter","android","async","chunk","first-timers-only","infinite-scroll","library","listview","recyclerview","ui-components"],"created_at":"2026-01-14T02:24:01.915Z","updated_at":"2026-01-14T02:24:02.541Z","avatar_url":"https://github.com/andviane.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.andviane/uncover/badge.svg)](https://mvnrepository.com/artifact/io.github.andviane/uncover) [![Build Status](https://travis-ci.org/andviane/google-books-android-viewer.svg?branch=master)](https://travis-ci.org/andviane/google-books-android-viewer) [![Javadoc](http://javadoc-badge.appspot.com/io.github.andviane/uncover.svg?label=Javadoc)](https://andviane.github.io/google-books-android-viewer/javadoc/index.html)\n\n# Infinite scrolling lists are both easy and efficient\n\n![Screen shot](https://raw.githubusercontent.com/andviane/google-books-android-viewer/master/info/sc1_sm.png \"Our proof of concept app\")\n\nThis project is a proof of concept demonstrator of Uncover library for Android. This library provides easy bridging between [RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html) or similar data model (synchronous access to single item by its index) and the typical web or database request that strongly favours asynchronous access in chunks (pages of the fixed size). It performs multiple management optimizations on how these pages should be fetched and prioritized: \n\n* Fetch in non overlapping pages rather than item by item.\n* Fetch most recently requested pages first, not last.\n* If the user swipes quickly, drop from the queue pages that, while not fetched, have already been scrolled out of the visible area.\n \nThe size of the items (hence the number of items per page) need not be constant, as it is often the case for descriptions of variable length or other similar texts. \n\nThe library sources are available in the Uncover folder under Apache 2.0 license. This project contains the source code. To fetch from the pgp-signed build of this library from [Maven Central](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.github.andviane%22%20AND%20a%3A%22uncover%22) as your dependency, simply include\n\n```java\n\ndependencies {\n    compile ('io.github.andviane:uncover:2.0.1@aar')\n    ..\n}    \n```    \ninto your Gradle build script. \n\n# Uncover library\n\nThe proposed library is centered around the data model [UncoveringDataModel](uncover/src/main/java/ames/com/uncover/UncoveringDataModel.java) that can be relatively easily tied to the data model of any UI list. This model provides item values by position, as well as the total number of items:\n```java\n    // Construction\n    UncoveringDataModel\u003cBook\u003e model = new UncoveringDataModel\u003cBook\u003e();\n    data.setPrimaryDataProvider(new BookDataProvider()); // Implement your own\n    data.setDataAvailableListener(this); // receive notifications when first results of the query arrive\n\n    // Serve as data model\n    Book book = data.getItem(position);\n    int itemCount = data.size();\n\n    // Set the query to show\n    data.setQuery(new Query(query));\n```    \n\nThe model can be tied to the existing recycler view and its adapter via method:\n```java\n    model.install(recyclerView, adapter);\n```    \n\nYes, yes, we are aware that in MVC the model should not know much about adapter, leave alone the view. However if the model must notify the view when it has data ready (so the region should be repainted), it needs the handle where to sent the event. And if the model needs to know which data are in more priority to provide, it must ask for the view about the currently visible region. This is not a typical one way MVC bus, so sorry about that.\n\nAs about adapter, it must be a [RecyclerView.Adapter](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html), nothing special is required. Its main task is to create views for complex items. See the [BookListAdapter](app/src/main/java/com/ames/books/presenter/BookListAdapter.java) class for our version. In our case adapter also creates model and attaches the primary data provider, but for sure you may design this differently.\n\nThe model itself fetches requests using your class that must implement the [PrimaryDataProvider](uncover/src/main/java/ames/com/uncover/primary/PrimaryDataProvider.java). It requests data asynchronuosly, in non-overapping chunks of the fixed (configurable) size, and prioritizes recent requests over older ones. While parallel requests are possible, they are under control: the maximal number is configurable (at most two are allowed by default).\n\nTo show the new data, simply call setQuery on the [UncoveringDataModel](uncover/src/main/java/ames/com/uncover/UncoveringDataModel.java). This method accepts the [Query](uncover/src/main/java/ames/com/uncover/primary/Query.java) that is passed to your [PrimaryDataProvider](uncover/src/main/java/ames/com/uncover/primary/PrimaryDataProvider.java). To be notified about the new data that the view must display, register the data listener as it is seen in [BookListAdapter](app/src/main/java/com/ames/books/presenter/BookListAdapter.java) class.\n\nModel also provides methods to get and set the state as Serializable. This is for saving instance state in cases like device reorientation; not for long term storage. Memory trimming is supported, see [BookListActivity](app/src/main/java/com/ames/books/BookListActivity.java).   \n\nThe source code of this library is located under the uncover folder. Use ../gradlew assembleRelease in this folder to build the .aar file if required. \n\n# Still too complex?\n\nWhat about the single Java class that does it all? Here is the class:\n\n```java\npackage com.ames.uncoverguide;\n\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.Log;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\n\nimport com.ames.uncover.UncoveringDataModel;\nimport com.ames.uncover.primary.PrimaryDataProvider;\nimport com.ames.uncover.primary.PrimaryRequest;\nimport com.ames.uncover.primary.PrimaryResponse;\nimport com.ames.uncover.primary.Query;\n\nimport java.util.ArrayList;\n\npublic class MainActivity extends AppCompatActivity {\n\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler);\n    final UncoveringDataModel\u003cString\u003e model = new UncoveringDataModel\u003c\u003e();\n\n    model.setPrimaryDataProvider(new PrimaryDataProvider\u003cString\u003e() {\n\n      @Override\n      public PrimaryResponse fetch(PrimaryRequest primaryRequest) {\n        // Observe logs so see the fetching\n        Log.i(\"Fetch\", \"Service call to fetch items\" + \n          primaryRequest.getFrom() + \"- \" + primaryRequest.getTo());\n\n        // Simulate pause. We are on the background thread now.\n        try { Thread.sleep(300); } catch (InterruptedException e) {}\n\n        ArrayList\u003cString\u003e data = new ArrayList\u003cString\u003e();\n        for (int p = primaryRequest.getFrom(); p \u003c primaryRequest.getTo(); p++) {\n          data.add(\"Item \" + p + \" by \" + primaryRequest.getQuery());\n        }\n        // Integer.MAX_VALUE items in total, enjoy scrolling\n        return new PrimaryResponse\u003cString\u003e(data, Integer.MAX_VALUE);\n      }\n    });\n\n    RecyclerView.Adapter adapter = new RecyclerView.Adapter() {\n\n      @Override\n      public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new RecyclerView.ViewHolder(new TextView(MainActivity.this)) { };\n      }\n\n      @Override\n      public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {\n        String content = model.getItem(position);\n        ((TextView) holder.itemView).setText(content);\n      }\n\n      @Override\n      public int getItemCount() {\n        return model.size();\n      }\n    };\n\n    model.install(recyclerView, adapter);\n\n    // Done, now just set the query to show. If we do not set the query, all we see is empty list.\n    // Add the button and set the query from its listener to observe the output change.\n    model.setQuery(new Query(\"abc\"));\n  }\n}\n```\n\nThis is off course the totally \"hello world\" demo: the primary data provider just bakes the data locally, the adapter works with TextView and, unlike in production code, the general style and design are optimized for quick and easy reading. We included a short delay on getting the data, to demonstrate that while looks simple, this is still a call on another thread, fetching is still chunked, and requests to get these chunks are still optimized. \n\nYou also need a layout to make this compile:\n\n```java\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cLinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    \u003e\n\n    \u003candroid.support.v7.widget.RecyclerView\n        android:id=\"@+id/recycler\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layoutManager=\"android.support.v7.widget.LinearLayoutManager\"/\u003e\n\n\u003c/LinearLayout\u003e\n\n```\n\nWith these two files, it is easy to build the app that is ready to run.\n\n# Description of the demo app\n\nTo demonstrate the library capabilities, the project provides Android application to demonstrate the \"infinite scroll\" over Google Books, displaying cover images and titles. This application is available under GPL v3.0.\n\nThe app uses Google Books API to scroll over the list of content that is returned as search result. It consists of two screens (fragments). The list screen one allows to scroll over search results, showing only cover thumb image, header, author and page count. The details screen that opens after tapping anywhere on the book item reveals more information about the particular book. Use the back button for returning back to the list.\n\nIf you just want quick preview, the Android app is available on [F-Droid](https://f-droid.org/packages/com.ames.books/) and [Google Play](https://play.google.com/store/apps/details?id=com.ames.books)\n\nThe app also fetches the book cover images that take much longer to appear. Image downloading and management is implemented with [Picasso](http://square.github.io/picasso/) and not directly related to the demonstration of Uncover library capabilities.\n\n\nSee also [the licensing conditions](https://developers.google.com/books/terms) of Google Books API. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandviane%2Fgoogle-books-android-viewer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandviane%2Fgoogle-books-android-viewer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandviane%2Fgoogle-books-android-viewer/lists"}