├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mvvm
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── mvvm
│ │ │ ├── MvvmApplication.java
│ │ │ ├── adapter
│ │ │ ├── BaseAdapter.java
│ │ │ ├── ContributorAdapter.java
│ │ │ └── SearchAdapter.java
│ │ │ ├── event
│ │ │ └── UserFollowEvent.java
│ │ │ ├── exception
│ │ │ ├── AccessDenyException.java
│ │ │ ├── ConversionException.java
│ │ │ ├── NetworkException.java
│ │ │ ├── Non200HttpException.java
│ │ │ └── UnKnowException.java
│ │ │ ├── http
│ │ │ ├── ApiServiceFactory.java
│ │ │ ├── ApiServiceFactory2.java
│ │ │ ├── ErrorCallAdapterFactory.java
│ │ │ ├── GitHubApi.java
│ │ │ ├── NetErrorType.java
│ │ │ ├── SearchApi.java
│ │ │ └── SynchronousCallAdapterFactory.java
│ │ │ ├── model
│ │ │ ├── AuthToken.java
│ │ │ ├── Book.java
│ │ │ ├── Contributor.java
│ │ │ ├── User.java
│ │ │ └── UserField.java
│ │ │ ├── ui
│ │ │ ├── BaseActivity.java
│ │ │ ├── ConverterActivity.java
│ │ │ ├── CustomSetterActivity.java
│ │ │ ├── DataBindSimpleActivity.java
│ │ │ ├── ELActivity.java
│ │ │ ├── GitHubContributorsActivity.java
│ │ │ ├── LoadingDialog.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SearchDebounceActivity.java
│ │ │ └── UpdateUserActivity.java
│ │ │ └── utils
│ │ │ ├── CrashHandler.java
│ │ │ ├── DividerItemDecoration.java
│ │ │ └── RecyclerViewUtils.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── erorr_loading.png
│ │ ├── ic_like_yellow.png
│ │ └── ic_like_yellowfull.png
│ │ ├── drawable
│ │ ├── placeholder_small_image.xml
│ │ ├── progress_medium_holo.xml
│ │ └── shape_toast_bg.xml
│ │ ├── layout
│ │ ├── activity_converter.xml
│ │ ├── activity_custom_setter.xml
│ │ ├── activity_el.xml
│ │ ├── activity_github_contributors.xml
│ │ ├── activity_main.xml
│ │ ├── activity_observable.xml
│ │ ├── activity_search_debounce.xml
│ │ ├── activity_simple.xml
│ │ ├── activity_update_user.xml
│ │ ├── dialog_common_loading.xml
│ │ ├── footer_loading_layout.xml
│ │ ├── item_contributor.xml
│ │ ├── item_search.xml
│ │ └── item_unknown.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── spinner_48_inner_holo.png
│ │ └── spinner_48_outer_holo.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── mvvm
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # Android Studio Navigation editor temp files
29 | .navigation/
30 |
31 | # Android Studio captures folder
32 | captures/
33 |
34 | .idea/
35 |
36 | *.iml
37 |
38 | *.iml
39 |
40 | *.iml
41 |
42 | *.iml
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 项目整体效果:
4 | 
5 |
6 |
7 |
8 | # Awesome-Android-MVVM
9 | - 什么是MVVM, 为什么需要 MVVM?
10 | - 如何在android中使用DataBinding实现MVVM架构?
11 |
12 | ## 什么是MVVM , 为什么需要MVVM?
13 |
14 | MVVM是Model-View-ViewModel的简写. 它是有三个部分组成:Model、View、ViewModel。
15 |
16 | Model:数据模型层。包含业务逻辑和校验逻辑。
17 |
18 | View:屏幕上显示的UI界面(layout、views)。
19 |
20 | ViewModel:View和Model之间的链接桥梁,处理视图逻辑。
21 |
22 | MVVM功能图如下:
23 |
24 | 
25 |
26 | MVVM架构通过ViewModel隔离了UI层和业务逻辑层,降低程序的耦合度。
27 |
28 | ### Android App 中MVC的不足
29 | 一般来说,我们开发Android App是基于MVC,由于MVC的普及和快速开发的特点,一个app从0开发一般都是基于MVC的。
30 |
31 | Activity、Fragment相当于C (Controller), 布局相当于V(View), 数据层相当于M(Model)
32 |
33 | 随着业务的增长,Controller里的代码会越来越臃肿,因为它不只要负责业务逻辑,还要控制View的展示。也就是说Activity、Fragment杂糅了Controller和View,耦合变大。并不能算作真正意义上的MVC。
34 |
35 | 编写代码基本的过程是这样的,在Activity、Fragment中初始化Views,然后拉取数据,成功后把数据填充到View里。
36 |
37 | `假如有如下场景`:
38 |
39 | > 我们基于MVC开发完第一版本,然后企业需要迭代2.0版本,并且UI界面变化比较大,业务变动较小,怎么办呢?
40 | 当2.0的所有东西都已经评审过后。这个时候,新建布局,然后开始按照新的效果图,进行UI布局。然后还要新建Activity、Fragment把相关逻辑和数据填充到新的View上。
41 | 如果业务逻辑比较复杂,需要从Activity、Fragment中提取上个版本的所有逻辑,这个时候自己可能就要晕倒了,因为一个复杂的业务,一个Activity几千行代码也是很常见的。千辛万苦做完提取完,可能还会出现很多bug。
42 |
43 | 一开始我尝试使用MVP架构, MVP功能图如下:
44 |
45 | 
46 |
47 |
48 |
49 | MVP 把视图层抽象到View接口,逻辑层抽象到 Presenter 接口,提到了代码的可读性。降低了视图逻辑和业务逻辑的耦合。
50 |
51 | 但是有 MVP 的不足:
52 |
53 | 1. 接口过多,一定程度影响了编码效率。
54 | 2. 业务逻辑抽抽象到Presenter中,较为复杂的界面Activity代码量依然会很多。
55 | 3. 导致Presenter的代码量过大。
56 |
57 |
58 | 这个时候MVVM就闪亮登场了。从上面的MVVM功能图我们知道:
59 |
60 | 1. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
61 | 在Android中,布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化。
62 | 2. 低耦合。以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成(具体代码请看后面)
63 | 甚至都不需要再Activity、Fragment去findViewById。这时候Activity、Fragment只需要做好的逻辑处理就可以了。
64 |
65 |
66 | 现在我们回到上面从app1.0到app2.0迭代的问题,如果用MVVM去实现那就比较简单,这个时候不需要动Activity、Fragment,
67 | 只需要把布局按照2.0版本的效果实现一遍即可。因为视图逻辑和数据填充已经在布局里了,这就是上面提到的可重用性。
68 |
69 | > 发展过程:
70 | MVC->MVP->MVVP
71 |
72 |
73 | ## Android中如何实现MVVM架构?
74 | Google在2015年的已经为我们DataBinding技术。下面就详细讲解如何使用DataBinding。
75 |
76 | ### 1,环境准备
77 | 在工程根目录build.gradle文件加入如下配置,把Android Gradle 插件升级到最新:
78 |
79 | dependencies {
80 | classpath 'com.android.tools.build:gradle:1.5.0'
81 | }
82 |
83 |
84 |
85 | 在app里的build.gradle文件加入如下配置,启用data binding 功能:
86 |
87 |
88 | dataBinding {
89 | enabled true
90 | }
91 |
92 |
93 |
94 | ### 2,来个简单的例子
95 |
96 | 实现上面效果的“Data Binding Simple Sample”
97 |
98 | #### data binding 布局格式和以往的有些区别:
99 |
100 | ```
101 |
102 |
103 |
104 |
105 |
106 |
107 | //normal layout
108 |
111 |
112 | ```
113 |
114 |
115 | - 布局的根节点为
116 |
117 | - 布局里使用的model 通过中的指定:
118 |
119 | ```
120 |
121 |
122 | ```
123 |
124 | - 设置空间属性的值,通过@{}语法来设置:
125 |
126 | ```
127 | android:text="@{user.firstName}"
128 |
129 | ```
130 |
131 | 下面是完整的布局实现:
132 |
133 | ```
134 |
135 |
136 |
137 |
138 |
139 |
142 |
143 |
144 |
151 |
152 |
153 |
158 |
159 |
165 |
166 |
167 |
172 |
173 |
180 |
181 |
187 |
188 |
194 |
195 |
202 |
203 |
204 |
205 |
206 |
212 |
213 |
220 |
221 |
222 |
223 |
224 |
225 |
226 | ```
227 |
228 |
229 |
230 | ####接下来实现数据模型类User:
231 |
232 | ```
233 | public class User {
234 |
235 | private String userName;
236 | private String realName;
237 | private String mobile;
238 | private int age;
239 |
240 | public User(String realName, String mobile) {
241 | this.realName = realName;
242 | this.mobile = mobile;
243 | }
244 |
245 | public User() {
246 | }
247 |
248 | //ignore getter and setter. see code for detail.
249 |
250 | }
251 |
252 | ```
253 |
254 | #### 在Activity中 绑定数据
255 |
256 | ```
257 | @Override
258 | public void onCreate(Bundle savedInstanceState) {
259 | super.onCreate(savedInstanceState);
260 | binding = DataBindingUtil.setContentView(this, R.layout.activity_simple);
261 | fetchData();
262 | }
263 |
264 | //模拟获取数据
265 | private void fetchData() {
266 | new AsyncTask() {
267 |
268 | @Override
269 | protected void onPreExecute() {
270 | super.onPreExecute();
271 | showLoadingDialog();
272 | }
273 |
274 | @Override
275 | protected Void doInBackground(Void... params) {
276 | try {
277 | Thread.sleep(2000);
278 | } catch (InterruptedException e) {
279 | e.printStackTrace();
280 | }
281 | return null;
282 | }
283 |
284 | @Override
285 | protected void onPostExecute(Void aVoid) {
286 | super.onPostExecute(aVoid);
287 | hideLoadingDialog();
288 | User user = new User("Chiclaim", "13512341234");
289 | binding.setUser(user);
290 | //binding.setVariable(com.mvvm.BR.user, user);
291 | }
292 | }.execute();
293 | }
294 | }
295 |
296 | ```
297 |
298 |
299 | > 通过DataBindingUtil.setContentView设置布局,通过binding类设置数据模型:
300 |
301 | ```
302 | binding.setUser(user);
303 | ```
304 |
305 |
306 | ### 3,布局详解
307 |
308 | #### import导入
309 | - 通过标签导入:
310 |
311 | ```
312 |
313 |
314 |
315 |
316 |
317 | android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"
318 | ```
319 |
320 | - 如果产生了冲突可以使用别名的方式:
321 |
322 | ```
323 |
324 |
325 |
326 |
327 | ```
328 |
329 | - 集合泛型左尖括号需要使用转译:
330 |
331 | ```
332 |
333 |
334 |
335 |
336 |
337 | ```
338 |
339 | - 使用导入类的静态字段和方法:
340 |
341 | ```
342 |
343 |
344 |
345 |
346 | …
347 |
351 |
352 | ```
353 |
354 | > 像JAVA一样,java.lang.*是自动导入的。
355 |
356 |
357 |
358 | #### Variables
359 | 在节点中使用来设置。
360 |
361 | ```
362 |
363 |
364 |
365 |
366 |
367 | ```
368 |
369 | - Binding类里将会包含通过variable设置name的getter和setter方法。如上面的setUser,getUser等。
370 |
371 | - 如果控件设置了id,那么该控件也可以在binding类中找到,这样就不需要findViewById来获取View了。
372 |
373 | #### 自定义Binding类名(Custom Binding Class Names)
374 |
375 | 以为根节点布局,android studio默认会自动产生一个Binding类。类名为根据布局名产生,如一个名为activity_simple的布局,它的Binding类为ActivitySimpleBinding,所在包为app_package/databinding。
376 | 当然也可以自定义Binding类的名称和包名:
377 |
378 | 1. `` 在app_package/databinding下生成CustomBinding;
379 |
380 | 2. `` 在app_package下生成CustomBinding;
381 |
382 | 3. `` 明确指定包名和类名。
383 |
384 |
385 |
386 | #### Includes
387 | ```
388 |
389 |
391 |
392 |
393 |
394 |
398 |
400 |
402 |
403 |
404 |
405 | ```
406 |
407 | name.xml 和 contact.xml都必须包含 ` `
408 |
409 | ### 4,DataBinding Obervable
410 |
411 | 在上面的一个例子上,数据是不变,随着用户的与app的交互,数据发生了变化,如何更新某个控件的值呢?
412 |
413 | 有如下几种方案(具体实现下载代码,运行,点击DataBinding Observable 按钮):
414 |
415 | 1. BaseObservable的方式
416 |
417 | 使User继承BaseObservable,在get方法上加上注解@Bindable,会在BR(BR类自动生成的)生成该字段标识(int)
418 | set方法里notifyPropertyChanged(BR.field);
419 |
420 | ```
421 | public class User extends BaseObservable{
422 |
423 | private String userName;
424 | private String realName;
425 |
426 | /**
427 | * 注意: 在BR里对应的常量为follow
428 | */
429 | private boolean isFollow;
430 |
431 |
432 | public User(String realName, String mobile) {
433 | this.realName = realName;
434 | this.mobile = mobile;
435 | }
436 |
437 | public User() {
438 | }
439 |
440 | @Bindable
441 | public boolean isFollow() {
442 | return isFollow;
443 | }
444 |
445 | public void setIsFollow(boolean isFollow) {
446 | this.isFollow = isFollow;
447 | notifyPropertyChanged(BR.follow);
448 | }
449 |
450 | @Bindable
451 | public String getUserName() {
452 | return userName;
453 | }
454 |
455 | public void setUserName(String userName) {
456 | this.userName = userName;
457 | notifyPropertyChanged(BR.userName);
458 | }
459 | ```
460 |
461 | > 如果数据发生变化通过set方法,view的值会自动更新,是不是很方便。
462 |
463 |
464 | 2. 通过ObserableField来实现
465 |
466 | ```
467 | public class UserField {
468 | public final ObservableField realName = new ObservableField<>();
469 | public final ObservableField mobile = new ObservableField<>();
470 |
471 | }
472 |
473 | ```
474 |
475 | 布局中使用:
476 |
477 | ```
478 |
479 |
480 |
486 |
487 | ```
488 |
489 | 代码中设置/改变数据:
490 |
491 | ```
492 | userField.realName.set("Chiclaim");
493 |
494 | ```
495 |
496 | 3. Observable Collections方式:
497 |
498 | ```
499 | private ObservableArrayMap map = new ObservableArrayMap();
500 |
501 | //设置数据
502 | map.put("realName", "Chiclaim");
503 | map.put("mobile", "110");
504 |
505 |
506 | ```
507 |
508 | 布局中使用:
509 |
510 | ```
511 |
518 |
519 | ```
520 |
521 | ### 5,下面通过DataBinding来实现列表
522 |
523 | 获取square公司retrofit代码贡献者数据列表,通过RecyclerView来实现。
524 | RecyclerView的Adapter实现的核心方法为两个onCreateViewHolder、onBindViewHolder方法和Item的ViewHolder。
525 |
526 | ```
527 | @Override
528 | public RecyclerView.ViewHolder onMyCreateViewHolder(ViewGroup parent, int viewType) {
529 | ItemContributorBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_contributor, parent, false);
530 | ContributorViewHolder viewHolder = new ContributorViewHolder(binding.getRoot());
531 | viewHolder.setBinding(binding);
532 | return viewHolder;
533 | }
534 |
535 | @Override
536 | public void onMyBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
537 | ContributorViewHolder contributorViewHolder = (ContributorViewHolder) viewHolder;
538 | Contributor contributor = getModel(position);
539 | contributorViewHolder.getBinding().setVariable(com.mvvm.BR.contributor, contributor);
540 | contributorViewHolder.getBinding().executePendingBindings();
541 | Picasso.with(mContext).load(contributor.getAvatar_url()).
542 | into(contributorViewHolder.binding.ivAvatar);
543 | }
544 |
545 |
546 | ```
547 |
548 | 通过setVariable方法来关联数据。
549 | getBinding().setVariable(com.mvvm.BR.contributor, contributor)
550 | 大家看到BR.contributor的contributor常量是怎么产生的?布局里的中的name属性值。如: 那么就会自动生成BR.book。`有点类似以前的R里面的id`。 有人会问了如果别的实体(model)也有相同的book属性怎么办?那他到底使用哪个呢?其实这是不会冲突,因为在不用的地方用,他的上下文(Binging)不一样,所以不会冲突。也是和以前的R里面的常量是一回事情。只是把它放到BR里面去了。所以我猜想BR的全称应该是(`Binding R`(R就是以前我们用的常量类))虽然官方没有说明。
551 |
552 | 通过 executePendingBindings 强制执行绑定数据。
553 |
554 | Item对应的VIewHolder
555 |
556 | ```
557 | public class ContributorViewHolder extends RecyclerView.ViewHolder {
558 |
559 | ItemContributorBinding binding;
560 |
561 | public void setBinding(ItemContributorBinding binding) {
562 | this.binding = binding;
563 | }
564 |
565 | public ItemContributorBinding getBinding() {
566 | return binding;
567 | }
568 |
569 | public ContributorViewHolder(View itemView) {
570 | super(itemView);
571 | }
572 | }
573 |
574 |
575 | ```
576 |
577 | > 在实现这个列表功能我使用了Retrofit+RxJava来做的。在这里引入了token机制,也就是说每个请求都会去把token带过去,然后服务器验证token是否过期,如果过期服务器就会返回403(没有访问权限),这个时候就需要去请求新的Token,然后再去请求列表数据(目前我在服务器端是设置了10s过期,服务器端代码也放在github上【[github地址](https://github.com/chiclaim/android_mvvm_server)】。
578 | > 假如不使用RxJava实现这样的嵌套逻辑请求就比较复杂了,如果过期,发起获取新Token的请求,成功去请求列表数据,这种嵌套的回调,可读性、可维护性很差。并且只要有请求的地方都需要加上这样的逻辑,当然普通的方式可以实现,只是不够优雅而已。使用RxJava就很优雅了,具体如何实现请查看代码。RxJava功能很强大,以后会通过单独的文章来进行说明。
579 |
580 |
581 |
582 |
583 | ### 6,EL 表达式(Expression Language)
584 |
585 | ####DataBinding支持的表达式有:
586 |
587 | 数学表达式: + - / * %
588 |
589 | 字符串拼接 +
590 |
591 | 逻辑表达式 && ||
592 |
593 | 位操作符 & | ^
594 |
595 | 一元操作符 + - ! ~
596 |
597 | 位移操作符 >> >>> <<
598 |
599 | 比较操作符 == > < >= <=
600 |
601 | instanceof
602 |
603 | 分组操作符 ()
604 |
605 | 字面量 - character, String, numeric, null
606 |
607 | 强转、方法调用
608 |
609 | 字段访问
610 |
611 | 数组访问 []
612 |
613 | 三元操作符 ?:
614 |
615 | #### 聚合判断(Null Coalescing Operator)语法 ‘??’
616 |
622 |
623 | 上面的意思是如果userName为null,则显示realName。
624 |
625 | #### Resource(资源相关)
626 | 在DataBinding语法中,可以吧resource作为其中的一部分。如:
627 |
628 | ```
629 | android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
630 |
631 | ```
632 |
633 | 除了支持dimen,还支持color、string、drawable、anim等。
634 |
635 | 注意,对mipmap图片资源支持还是有问题,目前只支持drawable。
636 |
637 | #### Event Binding (事件绑定)
638 |
639 | 事件处理器:
640 |
641 | ```
642 | public interface UserFollowEvent {
643 | void follow(View view);
644 | void unFollow(View view);
645 | }
646 |
647 | ```
648 |
649 | 布局中使用:
650 |
651 | ```
652 |
655 |
656 | android:onClick="@{user.isFollow ? event.unFollow : event.follow}"
657 | ```
658 |
659 | 在 Activity 实现该接口 UserFollowEvent :
660 |
661 | ```
662 | @Override
663 | public void follow(View view) {
664 | user.setIsFollow(true);
665 | }
666 |
667 | @Override
668 | public void unFollow(View view) {
669 | user.setIsFollow(false);
670 | }
671 | ```
672 |
673 | 效果如下所示:
674 |
675 | 
676 |
677 | 点击按钮后:
678 |
679 | 
680 |
681 |
682 | ### Custom Setter(自定义Setter方法)
683 | 有些时候我们需要自定义binding逻辑,如:在一个TextView上设置大小不一样的文字,这个时候就需要我们自定义binding逻辑了.
684 |
685 | 在比如我们为ImageView加载图片,通过总是通过类似这样的的代码来实现:
686 |
687 | ```
688 | Picasso.with(view.getContext()).load(url).into(view);
689 | ```
690 | 如果我们自定Setter方法,那么这些都可以是自动的。怎么实现呢?
691 |
692 | ```
693 | @BindingAdapter({"imageUrl"})
694 | public static void loadImage(ImageView view, String url) {
695 | Log.d("BindingAdapter", "loadImage(ImageView view, String url)");
696 | Log.d("BindingAdapter", url + "");
697 | Picasso.with(view.getContext()).load(url).into(view);
698 | }
699 | ```
700 | @BindingAdapter({"imageUrl"}) 这句话意味着我们自顶一个imageUrl属性,可以在布局文件中使用。当在布局文件中设置该属性的值发生改变,会自动
701 | 调用loadImage(ImageView view, String url)方法。
702 |
703 | 布局中使用:
704 |
705 | ```
706 |
711 | ```
712 |
713 | 再来看下如何实现:在一个TextView上设置大小不一样的文字(其实是一样的)
714 |
715 | ```
716 | @BindingAdapter("spanText")
717 | public static void setText(TextView textView, String value) {
718 | Log.d("BindingAdapter", "setText(TextView textView,String value)");
719 | SpannableString styledText = new SpannableString(value);
720 | styledText.setSpan(new TextAppearanceSpan(textView.getContext(), R.style.style0),
721 | 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
722 | styledText.setSpan(new TextAppearanceSpan(textView.getContext(), R.style.style1),
723 | 5, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
724 | styledText.setSpan(new TextAppearanceSpan(textView.getContext(), R.style.style0),
725 | 12, value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
726 | textView.setText(styledText, TextView.BufferType.SPANNABLE);
727 | }
728 | ```
729 |
730 | ```
731 |
735 | ```
736 |
737 | 注意:使用自定义Setter,需要使用dataBinding语法。以下用法是不对的:
738 |
739 | ```
740 |
744 | ```
745 |
746 | > 其他的例子就不一一在这里介绍了,详情可以查看github上的代码。
747 |
748 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 30
5 | buildToolsVersion "29.0.3"
6 |
7 | dataBinding {
8 | enabled true
9 | }
10 |
11 |
12 | defaultConfig {
13 | applicationId "com.mvvm"
14 | minSdkVersion 15
15 | targetSdkVersion 30
16 | versionCode 1
17 | versionName "1.0"
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | repositories {
28 |
29 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
30 |
31 | }
32 |
33 | dependencies {
34 | compile fileTree(dir: 'libs', include: ['*.jar'])
35 |
36 | testImplementation 'junit:junit:4.12'
37 | implementation 'androidx.appcompat:appcompat:1.2.0'
38 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
39 |
40 | //http module
41 | //snapshot worked with okhttp3.0.1
42 | //compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
43 | //compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
44 | //compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta4'
45 |
46 | //compile 'com.squareup.retrofit2:retrofit:2.0.0-SNAPSHOT'
47 | //compile 'com.squareup.retrofit2:converter-gson:2.0.0-SNAPSHOT'
48 | //compile 'com.squareup.retrofit2:retrofit-converters:2.0.0-SNAPSHOT'
49 |
50 | implementation 'com.squareup.retrofit:retrofit:1.9.0'
51 | implementation 'com.squareup.retrofit:retrofit-converters:1.9.0'
52 |
53 | implementation 'com.squareup.okhttp3:okhttp:4.9.0'
54 | implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.0.1'
55 | implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
56 |
57 | //image utils
58 | implementation 'com.squareup.picasso:picasso:2.5.2'
59 |
60 | //RxJava
61 | implementation 'io.reactivex:rxandroid:1.2.1'
62 | implementation 'io.reactivex:rxjava:1.3.0'
63 | implementation 'io.reactivex:rxjava-math:1.0.0'
64 | implementation 'com.jakewharton.rxbinding:rxbinding:1.0.0'
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/yuzhiqiang/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mvvm/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.mvvm;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
38 |
39 |
43 |
44 |
48 |
49 |
53 |
54 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/MvvmApplication.java:
--------------------------------------------------------------------------------
1 | package com.mvvm;
2 |
3 | import android.app.Application;
4 | import android.os.StrictMode;
5 |
6 | import com.mvvm.utils.CrashHandler;
7 |
8 | /**
9 | * Created by chiclaim on 2016/02/24
10 | */
11 | public class MvvmApplication extends Application {
12 |
13 | @Override
14 | public void onCreate() {
15 | if (BuildConfig.DEBUG) {
16 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
17 | .detectDiskReads()
18 | .detectDiskWrites()
19 | .detectNetwork() // or .detectAll() for all detectable problems
20 | .penaltyLog()
21 | .build());
22 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
23 | .detectLeakedSqlLiteObjects()
24 | .detectLeakedClosableObjects()
25 | .penaltyLog()
26 | .penaltyDeath()
27 | .build());
28 | }
29 |
30 | super.onCreate();
31 | CrashHandler.getInstance().init(this, "crash_log_mvvm");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/adapter/BaseAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.adapter;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import androidx.recyclerview.widget.RecyclerView;
9 |
10 | import com.mvvm.R;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | /**
17 | * Created by chiclaim on 2016/01/27
18 | */
19 | public abstract class BaseAdapter extends RecyclerView.Adapter {
20 |
21 |
22 | //内部维护数据源
23 | protected List list = new ArrayList<>();
24 |
25 | public boolean mShowFooter;
26 | public boolean mShowHead;
27 | public final static int FOOTER_TYPE = 99;
28 | protected Context mContext;
29 |
30 | protected LayoutInflater inflater;
31 |
32 | public BaseAdapter(Context context) {
33 | mContext = context;
34 | inflater = LayoutInflater.from(context);
35 | }
36 |
37 |
38 | public static class UnknownViewHolder extends RecyclerView.ViewHolder {
39 | public UnknownViewHolder(View itemView) {
40 | super(itemView);
41 | }
42 | }
43 |
44 | public static class Footer extends RecyclerView.ViewHolder {
45 | public Footer(View itemView) {
46 | super(itemView);
47 | }
48 | }
49 |
50 |
51 | public T getModel(int position) {
52 | if (list.size() == 0) {
53 | return null;
54 | }
55 | return list.get(position);
56 | }
57 |
58 | public int getModelSize() {
59 | return list.size();
60 | }
61 |
62 |
63 | public void showFooter() {
64 | hideHead();
65 | mShowFooter = true;
66 | notifyDataSetChanged();
67 | }
68 |
69 | public void hideFooter() {
70 | mShowFooter = false;
71 | notifyDataSetChanged();
72 | }
73 |
74 | public void showHead() {
75 | mShowHead = true;
76 | hideFooter();
77 | }
78 |
79 | public void hideHead() {
80 | mShowHead = false;
81 | }
82 |
83 | @Override
84 | public int getItemViewType(int position) {
85 | if (mShowFooter && position == getMyItemCount()) {
86 | return FOOTER_TYPE;
87 | }
88 | return getMyItemViewType(position);
89 | }
90 |
91 | @Override
92 | public void onBindViewHolder(RecyclerView.ViewHolder arg0, int positon) {
93 | int type = getItemViewType(positon);
94 | switch (type) {
95 | case FOOTER_TYPE:
96 | break;
97 | default:
98 | onMyBindViewHolder(arg0, mShowHead ? positon - 1 : positon);
99 | break;
100 | }
101 | }
102 |
103 | @Override
104 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
105 | switch (viewType) {
106 | case FOOTER_TYPE:
107 | return new Footer(getLayout(R.layout.footer_loading_layout, viewGroup));
108 | default:
109 | return onMyCreateViewHolder(viewGroup, viewType);
110 | }
111 | }
112 |
113 | @Override
114 | public int getItemCount() {
115 | return getMyItemCount() + (mShowFooter ? 1 : 0) + (mShowHead ? 1 : 0);
116 | }
117 |
118 | /**
119 | * 替代getItemViewType
120 | *
121 | * @return
122 | */
123 | public int getMyItemCount() {
124 | return list.size();
125 | }
126 |
127 | /**
128 | * 替代onCreateViewHolder
129 | *
130 | * @param parent
131 | * @param viewType
132 | * @return
133 | */
134 | public abstract RecyclerView.ViewHolder onMyCreateViewHolder(ViewGroup parent, int viewType);
135 |
136 | /**
137 | * 替代onBindViewHolder
138 | *
139 | * @param viewHolder
140 | * @param position
141 | */
142 | public abstract void onMyBindViewHolder(RecyclerView.ViewHolder viewHolder, int position);
143 |
144 | /**
145 | * 替代getItemViewType
146 | *
147 | * @param position
148 | * @return
149 | */
150 | public abstract int getMyItemViewType(int position);
151 |
152 |
153 | public void appendItems(List items) {
154 | if (items == null || items.isEmpty()) {
155 | return;
156 | }
157 | //int startPosition = list.size();
158 | list.addAll(items);
159 | //notifyItemRangeInserted(startPosition, items.size());
160 | notifyDataSetChanged();
161 | }
162 |
163 | public void appendItem(T item) {
164 | if (item == null) {
165 | return;
166 | }
167 | appendItems(Arrays.asList(item));
168 | }
169 |
170 |
171 | public void removeAll() {
172 | list.clear();
173 | hideFooter();
174 | notifyDataSetChanged();
175 | }
176 |
177 | public View getLayout(int layoutId, ViewGroup parent) {
178 | return inflater.inflate(layoutId, parent, false);
179 | }
180 |
181 |
182 | public RecyclerView.ViewHolder getUnKnowViewHolder(ViewGroup parent) {
183 | return new UnknownViewHolder(getLayout(R.layout.item_unknown, parent));
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/adapter/ContributorAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.adapter;
2 |
3 | import android.content.Context;
4 | import androidx.databinding.DataBindingUtil;
5 | import androidx.recyclerview.widget.RecyclerView;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import com.mvvm.R;
10 | import com.mvvm.databinding.ItemContributorBinding;
11 | import com.mvvm.model.Contributor;
12 | import com.squareup.picasso.Picasso;
13 |
14 |
15 | /**
16 | * Created by chiclaim on 2016/01/27
17 | */
18 | public class ContributorAdapter extends BaseAdapter {
19 |
20 | public ContributorAdapter(Context context) {
21 | super(context);
22 | }
23 |
24 | @Override
25 | public RecyclerView.ViewHolder onMyCreateViewHolder(ViewGroup parent, int viewType) {
26 | ItemContributorBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_contributor, parent, false);
27 | ContributorViewHolder viewHolder = new ContributorViewHolder(binding.getRoot());
28 | viewHolder.setBinding(binding);
29 | return viewHolder;
30 | }
31 |
32 | @Override
33 | public void onMyBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
34 | ContributorViewHolder contributorViewHolder = (ContributorViewHolder) viewHolder;
35 | Contributor contributor = getModel(position);
36 | contributorViewHolder.getBinding().setVariable(com.mvvm.BR.contributor, contributor);
37 | contributorViewHolder.getBinding().executePendingBindings();
38 | Picasso.with(mContext).load(contributor.getAvatar_url()).
39 | into(contributorViewHolder.binding.ivAvatar);
40 | }
41 |
42 | @Override
43 | public int getMyItemViewType(int position) {
44 | return 0;
45 | }
46 |
47 |
48 | public class ContributorViewHolder extends RecyclerView.ViewHolder {
49 |
50 | ItemContributorBinding binding;
51 |
52 | public void setBinding(ItemContributorBinding binding) {
53 | this.binding = binding;
54 | }
55 |
56 | public ItemContributorBinding getBinding() {
57 | return binding;
58 | }
59 |
60 | public ContributorViewHolder(View itemView) {
61 | super(itemView);
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/adapter/SearchAdapter.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.adapter;
2 |
3 | import android.content.Context;
4 | import androidx.databinding.DataBindingUtil;
5 | import androidx.recyclerview.widget.RecyclerView;
6 | import android.view.ViewGroup;
7 |
8 | import com.mvvm.BR;
9 | import com.mvvm.R;
10 | import com.mvvm.databinding.ItemSearchBinding;
11 |
12 | /**
13 | * Created by chiclaim on 2016/02/26
14 | */
15 | public class SearchAdapter extends BaseAdapter {
16 |
17 |
18 | public SearchAdapter(Context context) {
19 | super(context);
20 | }
21 |
22 | @Override
23 | public RecyclerView.ViewHolder onMyCreateViewHolder(ViewGroup parent, int viewType) {
24 | ItemSearchBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_search, parent, false);
25 | return new ItemViewHolder(binding);
26 | }
27 |
28 | @Override
29 | public void onMyBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
30 | ItemViewHolder itemViewHolder = (ItemViewHolder) viewHolder;
31 | String value = getModel(position);
32 | itemViewHolder.getBinding().setVariable(BR.value, value);
33 | itemViewHolder.getBinding().executePendingBindings();
34 | }
35 |
36 | @Override
37 | public int getMyItemViewType(int position) {
38 | return 0;
39 | }
40 |
41 |
42 | class ItemViewHolder extends RecyclerView.ViewHolder {
43 |
44 | ItemSearchBinding itemBinding;
45 |
46 | public ItemViewHolder(ItemSearchBinding itemBinding) {
47 | super(itemBinding.getRoot());
48 | this.itemBinding = itemBinding;
49 | }
50 |
51 | public ItemSearchBinding getBinding() {
52 | return itemBinding;
53 | }
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/event/UserFollowEvent.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.event;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by chiclaim on 2016/02/21
7 | */
8 | public interface UserFollowEvent {
9 | void follow(View view);
10 | void unFollow(View view);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/exception/AccessDenyException.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.exception;
2 |
3 | /**
4 | * Created by chiclaim on 2016/02/25
5 | */
6 | public class AccessDenyException extends RuntimeException {
7 | public AccessDenyException(String detailMessage) {
8 | super(detailMessage);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/exception/ConversionException.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.exception;
2 |
3 | /**
4 | * Created by chiclaim on 2016/02/26
5 | */
6 | public class ConversionException extends RuntimeException {
7 | public ConversionException(String detailMessage) {
8 | super(detailMessage);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/exception/NetworkException.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.exception;
2 |
3 | /**
4 | * Created by chiclaim on 2016/02/25
5 | */
6 | public class NetworkException extends RuntimeException {
7 | public NetworkException(String detailMessage) {
8 | super(detailMessage);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/exception/Non200HttpException.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.exception;
2 |
3 | /**
4 | * A non-200 HTTP status code was received from the server
5 | * Created by chiclaim on 2016/02/26
6 | */
7 | public class Non200HttpException extends RuntimeException {
8 | public Non200HttpException(String detailMessage) {
9 | super(detailMessage);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/exception/UnKnowException.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.exception;
2 |
3 | /**
4 | * Created by chiclaim on 2016/02/26
5 | */
6 | public class UnKnowException extends RuntimeException {
7 | public UnKnowException(String detailMessage) {
8 | super(detailMessage);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/http/ApiServiceFactory.java:
--------------------------------------------------------------------------------
1 | package com.mvvm.http;
2 |
3 | import android.util.Log;
4 |
5 | import com.mvvm.exception.AccessDenyException;
6 | import com.mvvm.exception.ConversionException;
7 | import com.mvvm.exception.UnKnowException;
8 |
9 | import retrofit.ErrorHandler;
10 | import retrofit.RequestInterceptor;
11 | import retrofit.RestAdapter;
12 | import retrofit.RetrofitError;
13 |
14 | /**
15 | * Created by chiclaim on 2016/01/26
16 | */
17 | public class ApiServiceFactory {
18 |
19 | //server source code please see:
20 | // https://github.com/chiclaim/android_mvvm_server
21 | private static final String BASE_URL = "http://192.168.1.109:8080/AndroidMvvmServer";
22 |
23 | private static RequestInterceptor requestInterceptor = new RequestInterceptor() {
24 | @Override
25 | public void intercept(RequestFacade request) {
26 | request.addHeader("Authorization", "test");
27 | }
28 | };
29 |
30 | private static class NetWorkErrorHandler implements ErrorHandler {
31 | @Override
32 | public Throwable handleError(RetrofitError error) {
33 | retrofit.client.Response r = error.getResponse();
34 | if (r != null && r.getStatus() == 401) {
35 | Log.e("ErrorHandler", "---------> access deny code=401");
36 | return new AccessDenyException(error.getMessage());
37 | } else if (error.getKind() == RetrofitError.Kind.NETWORK) {
38 | Log.e("ErrorHandler", "---------> An IOException occurred while communicating to the server");
39 | //return new NetworkException(cause.getMessage());
40 | } else if (error.getKind() == RetrofitError.Kind.HTTP) {
41 | Log.e("ErrorHandler", "---------> A non-200 HTTP status code was received from the server");
42 | //return new Non200HttpException(cause.getMessage());
43 | } else if (error.getKind() == RetrofitError.Kind.CONVERSION) {
44 | Log.e("ErrorHandler", "---------> An exception was thrown while (de)serializing a body");
45 | return new ConversionException(error.getMessage());
46 | } else if (error.getKind() == RetrofitError.Kind.UNEXPECTED) {
47 | Log.e("ErrorHandler", "---------> An internal error occurred while attempting to execute a request. " +
48 | "It is best practice to re-throw this exception so your application crashes.");
49 | return new UnKnowException(error.getMessage());
50 | }
51 | return error.getCause();
52 | }
53 | }
54 |
55 | private static RestAdapter restAdapter = new RestAdapter
56 | .Builder()
57 | .setLogLevel(RestAdapter.LogLevel.FULL)
58 | .setEndpoint(BASE_URL)
59 | .setErrorHandler(new NetWorkErrorHandler())
60 | .setRequestInterceptor(requestInterceptor)
61 | .build();
62 |
63 |
64 | public static S createService(Class serviceClazz) {
65 | return restAdapter.create(serviceClazz);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/http/ApiServiceFactory2.java:
--------------------------------------------------------------------------------
1 | //package com.mvvm.http;
2 | //
3 | //import android.text.TextUtils;
4 | //
5 | //import java.io.IOException;
6 | //
7 | //import okhttp3.Interceptor;
8 | //import okhttp3.OkHttpClient;
9 | //import okhttp3.Request;
10 | //import okhttp3.Response;
11 | //import okhttp3.logging.HttpLoggingInterceptor;
12 | //import retrofit2.Retrofit;
13 | //import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
14 | //import retrofit2.converter.gson.GsonConverterFactory;
15 | //
16 | ///**
17 | // * Created by chiclaim on 2016/01/26
18 | // */
19 | //public class ApiServiceFactory2 {
20 | //
21 | // //private static final String BASE_URL = "https://api.github.com/";
22 | // private static final String BASE_URL = "http://192.168.1.109:8080/JavaWebHttp2/";
23 | //
24 | // static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
25 | // static HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
26 | //
27 | //
28 | // private static Retrofit.Builder builder = new Retrofit.Builder()
29 | // .baseUrl(BASE_URL)
30 | // //.addCallAdapterFactory(ErrorCallAdapterFactory.create())
31 | // .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
32 | // .addCallAdapterFactory(SynchronousCallAdapterFactory.create())
33 | // .addConverterFactory(GsonConverterFactory.create());
34 | //
35 | // public static S createService(Class serviceClazz) {
36 | // return createService(serviceClazz, null);
37 | // }
38 | //
39 | // static {
40 | // // set your desired log level
41 | // logging.setLevel(HttpLoggingInterceptor.Level.BODY);
42 | // httpClient.interceptors().add(logging);
43 | //
44 | // //httpClient.addNetworkInterceptor
45 | //
46 | // }
47 | //
48 | //
49 | // public static S createService(Class serviceClazz, final String authorization) {
50 | //
51 | // //Gson gson = new GsonBuilder()
52 | // // .setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'")
53 | // // .create();
54 | //
55 | // //httpClient.interceptors().clear();
56 | //
57 | // if (!TextUtils.isEmpty(authorization)) {
58 | // httpClient.interceptors().add(new Interceptor() {
59 | // @Override
60 | // public Response intercept(Chain chain) throws IOException {
61 | // Request original = chain.request();
62 | // //addHead()
63 | // Request.Builder requestBuilder = original.newBuilder()
64 | // .header("Authorization", authorization)
65 | // .header("Accept", "applicaton/json")
66 | // .method(original.method(), original.body());
67 | //
68 | // Request request = requestBuilder.build();
69 | // return chain.proceed(request);
70 | // }
71 | // });
72 | // }
73 | //
74 | //
75 | // OkHttpClient hClient = httpClient.build();
76 | // Retrofit retrofit = builder.client(hClient).build();
77 | // return retrofit.create(serviceClazz);
78 | // }
79 | //
80 | //}
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mvvm/http/ErrorCallAdapterFactory.java:
--------------------------------------------------------------------------------
1 | //package com.mvvm.http;
2 | //
3 | //import com.mvvm.exception.AccessDenyException;
4 | //
5 | //import java.io.IOException;
6 | //import java.lang.annotation.Annotation;
7 | //import java.lang.reflect.Type;
8 | //
9 | //import retrofit2.Call;
10 | //import retrofit2.CallAdapter;
11 | //import retrofit2.Response;
12 | //import retrofit2.Retrofit;
13 | //
14 | //public class ErrorCallAdapterFactory extends CallAdapter.Factory {
15 | // public static CallAdapter.Factory create() {
16 | // return new ErrorCallAdapterFactory();
17 | // }
18 | //
19 | // @Override
20 | // public CallAdapter