• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..16-Feb-2021-

mostvisited/H16-Feb-2021-1,102709

tile/H16-Feb-2021-2,1691,310

DestructionObserver.javaH A D16-Feb-2021487 132

ImageFetcher.javaH A D16-Feb-20212.4 KiB7333

NavigationRecorder.javaH A D16-Feb-20215 KiB12277

OWNERSH A D16-Feb-202169 11

OfflinableSuggestion.javaH A D16-Feb-2021977 3110

README.mdH A D16-Feb-20219 KiB201151

SiteSuggestion.javaH A D16-Feb-20213 KiB8953

SuggestionsConfig.javaH A D16-Feb-20213.6 KiB10352

SuggestionsDependencyFactory.javaH A D16-Feb-20211.9 KiB5132

SuggestionsMetrics.javaH A D16-Feb-20212.7 KiB7143

SuggestionsNavigationDelegate.javaH A D16-Feb-20212 KiB4928

SuggestionsOfflineModelObserver.javaH A D16-Feb-20216.5 KiB16098

SuggestionsUiDelegate.javaH A D16-Feb-20211.2 KiB369

SuggestionsUiDelegateImpl.javaH A D16-Feb-20212.2 KiB7449

ThumbnailGradient.javaH A D16-Feb-20215.9 KiB14980

README.md

1# Content Suggestions UI: Architecture and Package Overview
2
3## Introduction
4
5This document describes the architecture for the content suggestions UI. See the
6[internal project page](https://goto.google.com/chrome-content-suggestions) for
7more info about the project. This document covers the general principles and
8some aspects of the implementation, to be seen both as explanation of our
9solution and guidelines for future developments.
10
11
12## Goals
13
14- **Make development easier.** Code should be well-factored. Test coverage
15  should be ubiquitous, and writing tests shouldn't be burdensome. Support for
16  obsolete features should be easy to remove.
17
18- **Allow for radical UI changes.** The core architecture of the package should
19  be structured to allow for flexibility and experimentation in the UI. This
20  means it generally shouldn't be tied to any particular UI surface, and
21  specifically that it is flexible enough to accomodate both the current NTP and
22  its evolutions.
23
24
25## Principles
26
27- **Decoupling.** Components should not depend on other components explicitly.
28  Where items interact, they should do so through interfaces or other
29  abstractions that prevent tight coupling.
30
31- **Encapsulation.** A complement to decoupling is encapsulation. Components
32  should expose little specifics about their internal state. Public APIs should
33  be as small as possible. Architectural commonalities (for example, the use of
34  a common interface for ViewHolders) will mean that the essential interfaces
35  for complex components can be both small and common across many
36  implementations. Overall the combination of decoupling and encapsulation means
37  that components of the package can be rearranged or removed without impacting
38  the others.
39
40- **Separation of Layers.** Components should operate at a specific layer in the
41  adapter/view holder system, and their interactions with components in other
42  layers should be well defined.
43
44
45## Core Anatomy
46
47### The RecyclerView / Adapter / ViewHolder pattern
48
49The UI is conceptually a list of views, and as such we are using the standard
50system component for rendering long and/or complex lists: the
51[RecyclerView][rv_doc]. It comes with a couple of classes that work together to
52provide and update data, display views and recycle them when they move out of
53the viewport.
54
55Summary of how we use that pattern for suggestions:
56
57- **RecyclerView:** The list itself. It asks the Adapter for data for a given
58  position, decides when to display it and when to reuse existing views to
59  display new data. It receives user interactions, so behaviours such as
60  swipe-to-dismiss or snap scrolling are implemented at the level of the
61  RecyclerView.
62
63- **Adapter:** It holds the data and is the RecyclerView's feeding mechanism.
64  For a given position requested by the RecyclerView, it returns the associated
65  data, or creates ViewHolders for a given data type. Another responsibility of
66  the Adapter is being a controller in the system by forwarding notifications
67  between ViewHolders and the RecyclerView, requesting view updates, etc.
68
69- **ViewHolder:** They hold views and allow efficiently updating the data they
70  display. There is one for each view created, and as views enter and exit the
71  viewport, the RecyclerView requests them to update the view they hold for the
72  data retrieved from the Adapter.
73
74For more info, check out [this tutorial][detailed tutorial] that gives more
75explanations.
76
77A specificity of our usage of this pattern is that our data is organised as a
78tree rather than as a flat list (see the next section for more info on that), so
79the Adapter also has the role of making that tree appear flat for the
80RecyclerView.
81
82[rv_doc]: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
83[detailed tutorial]: http://willowtreeapps.com/ideas/android-fundamentals-working-with-the-recyclerview-adapter-and-viewholder-pattern/
84
85
86### Representation of the data: the node tree
87
88#### Problem
89
90- RecyclerView.Adapter exposes items as a single list.
91- The Cards UI has nested structure: the UI has a list of card sections, each
92  section has a list of cards, etc.
93- There are dependencies between nearby items: e.g. a status card is shown if
94  the list of suggestion cards is empty.
95- We want to avoid tight coupling: A single adapter coupling the logic for
96  different UI components together, a list of items coupling the model
97  (SnippetArticle) to the controller, etc.
98- Triggering model changes in parts of the UI is complicated, since item
99  offsets need to be adjusted globally.
100
101#### Solution
102
103Build a tree of adapter-like nodes.
104
105- Each node represents any number of items:
106  * A single node can represent a homogenous list of items.
107  * An "optional" node can represent zero or one item (allowing toggling its
108    visibility).
109- Inner nodes dispatch methods to their children.
110- Child nodes notify their parent about model changes. Offsets can be adjusted
111  while bubbling changes up the hierarchy.
112- Polymorphism allows each node to represent / manage its own items however it
113  wants.
114
115Making modification to the TreeNode:
116
117- ChildNode silently swallows notifications before its parent is assigned.
118  This allows constructing tree or parts thereof without sending spurious
119  notifications during adapter initialization.
120- Attaching a child to a node sets its parent and notifies about the number of
121  items inserted.
122- Detaching a child notifies about the number of items removed and clears the
123  parent.
124- The number of items is cached and updated when notifications are sent to the
125  parent, meaning that a node is _required_ to send notifications any time its
126  number of items changes.
127
128As a result of this design, tree nodes can be added or removed depending on the
129current setup and the experiments enabled. Since nothing is hardcoded, only the
130initialisation changes. Nodes are specialised and are concerned only with their
131own functioning and don't need to care about their neighbours.
132
133
134### Interactions with the rest of Chrome
135
136To make the package easily testable and coherent with our principles,
137interactions with the rest of Chrome goes through a set of interfaces. They are
138implemented by objects passed around during the object's creation. See their
139javadoc and the unit tests for more info.
140
141- [`SuggestionsUiDelegate`](SuggestionsUiDelegate.java)
142- [`SuggestionsNavigationDelegate`](SuggestionsNavigationDelegate.java)
143- [`SuggestionsMetrics`](SuggestionsMetrics.java)
144- [`SuggestionsRanker`](SuggestionsRanker.java)
145- [`ContextMenuManager.Delegate`](../ntp/ContextMenuManager.java)
146
147
148## Appendix
149
150### Sample operations
151
152#### 1. Inserting an item
153
154Context: A node is notified that it should be inserted. This is simply mixing
155the standard RecyclerView pattern usage from the system framework with our data
156tree.
157
158Sample code path: [`SigninPromo.SigninObserver#onSignedOut()`][cs_link_1]
159
160- A Node wants to insert a new child item.
161- The Node notifies its parent of the range of indices to be inserted
162- Parent maps the range of indices received from the node to is own range and
163  propagates the notification upwards, repeating this until it reaches the root
164  node, which is the Adapter.
165- The Adapter notifies the RecyclerView that it has new data about a range of
166  positions where items should be inserted.
167- The RecyclerView requests from the Adapter the view type of the data at that
168  position.
169- The Adapter propagates the request down the tree, the leaf for that position
170  eventually returns a value
171- If the RecyclerView does not already have a ViewHolder eligible to be recycled
172  for the returned type, it asks the Adapter to create a new one.
173- The RecyclerView asks the Adapter to bind the data at the considered position
174  to the ViewHolder it allocated for it.
175- The Adapter transfers the ViewHolder down the tree to the leaf associated to
176  that position
177- The leaf node updates the view holder with the data to be displayed.
178- The RecyclerView performs the associated canned animation, attaches the view
179  and displays it.
180
181[cs_link_1]: https://cs.chromium.org/chromium/src/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java?l=174&rcl=da4b23b1d2a82705f7f4fdfb6c9c8de00341c0af
182
183#### 2. Modifying an existing item
184
185Context: A node is notified that it needs to update some of the data that is
186already displayed. In this we also rely on the RecyclerView mechanism of partial
187updates that is supported in the framework, but our convention is to use
188callbacks as notification payload.
189
190Sample code path: [`TileGrid#onTileOfflineBadgeVisibilityChanged()`][cs_link_2]
191
192- A Node wants to update the view associated to a currently bound item.
193- The Node notifies its parent that a change happened at a specific position,
194  using a callback as payload.
195- The notification bubbles up to the Adapter, which notifies the RecyclerView.
196- The RecyclerView calls back to the Adapter with the ViewHolder to modify and
197  the payload it received.
198- The Adapter runs the callback, passing the ViewHolder as argument.
199
200[cs_link_2]: https://cs.chromium.org/chromium/src/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGrid.java?l=78&rcl=da4b23b1d2a82705f7f4fdfb6c9c8de00341c0af
201