├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── activeandroid_3_1_0_SNAPSHOT.xml │ ├── animated_vector_drawable_23_2_0.xml │ ├── appcompat_v7_23_2_0.xml │ ├── converter_gson_2_0_0_beta4.xml │ ├── design_23_2_0.xml │ ├── gson_2_6_2.xml │ ├── hamcrest_core_1_3.xml │ ├── jsr305_1_3_9.xml │ ├── junit_4_12.xml │ ├── okhttp_3_2_0.xml │ ├── okio_1_6_0.xml │ ├── recyclerview_v7_23_2_0.xml │ ├── retrofit_2_0_0_beta4.xml │ ├── roboguice_3_0_1.xml │ ├── rxandroid_1_1_0.xml │ ├── rxjava_1_1_1.xml │ ├── support_annotations_23_2_0.xml │ ├── support_v4_23_2_0.xml │ └── support_vector_drawable_23_2_0.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── vcs.xml └── workspace.xml ├── Data-Driven-Architecture-Android.iml ├── LICENSE ├── README.md ├── TrimData.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mrpeak │ │ └── trimdata │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── appIcon-web.png │ ├── app_launcher-web.png │ ├── java │ │ └── com │ │ │ └── mrpeak │ │ │ ├── ppkit │ │ │ └── android │ │ │ │ └── ObservableUtil.java │ │ │ └── trimdata │ │ │ ├── application │ │ │ ├── MainTabActivity.java │ │ │ ├── common │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── BaseBusinessObject.java │ │ │ │ ├── BaseDataHandler.java │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── BaseRecyclerViewAdapter.java │ │ │ │ ├── BaseViewHolder.java │ │ │ │ ├── ItemListFragment.java │ │ │ │ ├── MyRoboActivity.java │ │ │ │ └── MyRoboFragment.java │ │ │ ├── feed │ │ │ │ ├── feedDetail │ │ │ │ │ ├── FeedDetailActivity.java │ │ │ │ │ ├── FeedDetailBO.java │ │ │ │ │ ├── FeedDetailDH.java │ │ │ │ │ ├── IFeedDetailBO.java │ │ │ │ │ ├── IFeedDetailDH.java │ │ │ │ │ └── model │ │ │ │ │ │ ├── FeedDetail.java │ │ │ │ │ │ └── FeedDetailGitHubRepo.java │ │ │ │ └── feedStream │ │ │ │ │ ├── FeedStreamAdapter.java │ │ │ │ │ ├── FeedStreamBO.java │ │ │ │ │ ├── FeedStreamDH.java │ │ │ │ │ ├── FeedStreamFragment.java │ │ │ │ │ ├── IFeedStreamBO.java │ │ │ │ │ ├── IFeedStreamDH.java │ │ │ │ │ ├── model │ │ │ │ │ ├── FeedItem.java │ │ │ │ │ └── FeedItemGitHubRepo.java │ │ │ │ │ └── view │ │ │ │ │ ├── FeedItemGitHubRepoVH.java │ │ │ │ │ └── FeedItemVH.java │ │ │ └── setting │ │ │ │ └── SettingListFragment.java │ │ │ ├── core │ │ │ └── TrimApplication.java │ │ │ ├── dal │ │ │ ├── DalBase.java │ │ │ ├── DalFactory.java │ │ │ ├── DalModule.java │ │ │ ├── EventSource.java │ │ │ ├── IDalBase.java │ │ │ ├── IGitHubRepoDal.java │ │ │ ├── imp │ │ │ │ └── GitHubRepoDalImp.java │ │ │ └── rawModel │ │ │ │ ├── GitHubRepository.java │ │ │ │ └── RawModelBase.java │ │ │ └── service │ │ │ ├── IFeedStreamService.java │ │ │ ├── IGitHubService.java │ │ │ ├── ServiceBase.java │ │ │ ├── ServiceFactory.java │ │ │ ├── ServiceModule.java │ │ │ ├── cache │ │ │ ├── FeedStreamServiceCache.java │ │ │ └── GitHubServiceCache.java │ │ │ ├── imp │ │ │ ├── FeedStreamServiceImp.java │ │ │ └── GitHubServiceImp.java │ │ │ ├── network │ │ │ ├── PPHttpRequest.java │ │ │ ├── PPHttpRequestDelegate.java │ │ │ ├── github │ │ │ │ ├── GitHubRepositoryListRequest.java │ │ │ │ ├── GitHubRequest.java │ │ │ │ └── GitHubRequestProvider.java │ │ │ └── retrofitModel │ │ │ │ └── RfRepository.java │ │ │ └── test │ │ │ ├── FeedStreamServiceTest.java │ │ │ └── GitHubServiceTest.java │ └── res │ │ ├── layout │ │ ├── activity_feed_detail.xml │ │ ├── activity_main.xml │ │ ├── content_feed_detail.xml │ │ ├── fragment_itemlist_github_repo.xml │ │ ├── fragment_itemlist_list.xml │ │ └── fragment_setting_list.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── app_launcher.png │ │ ├── mipmap-mdpi │ │ └── app_launcher.png │ │ ├── mipmap-xhdpi │ │ └── app_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── app_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── app_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mrpeak │ └── trimdata │ └── 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 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Data-Driven-Architecture-Android -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/libraries/activeandroid_3_1_0_SNAPSHOT.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/animated_vector_drawable_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/converter_gson_2_0_0_beta4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/design_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/gson_2_6_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/jsr305_1_3_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/okhttp_3_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/okio_1_6_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/recyclerview_v7_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/retrofit_2_0_0_beta4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/roboguice_3_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/rxandroid_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/rxjava_1_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/support_vector_drawable_23_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | $USER_HOME$/.subversion 48 | 49 | 50 | 51 | 52 | 53 | 1.8 54 | 55 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Data-Driven-Architecture-Android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 gao feng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 之前的文章里介绍了如何搭建iOS平台下的数据驱动型架构,iOS和Android系统在平台特性和应用场景下其实有很高的相似性,在不少架构的整体实现思路上基本都一致,只是由于语言特性的差异在实现方式上会有些不同而已。简而言之,内功心法一致,变的只是招式。这里我们用相同的思路来搭建Android系统平台下的DDA(Data Driven Architecture)架构。 2 | 3 | ## **1.数据驱动** 4 | 5 | 数据驱动是一种思想,[数据驱动型编程](https://en.wikipedia.org/wiki/Data-driven_programming)是一种编程范式。基于数据驱动的编程,基于事件的编程,以及近几年业界关注的响应式编程,本质其实都是观察者模型。数据驱动定义了data和acton之间的关系,传统的思维方式是从action开始,一个action到新的action,不同的action里面可能会触发data的修改。数据驱动则是反其道而行之,以data的变化为起点,data的变化触发新的action,action改变data之后再触发另一个action。如果data触发action的逻辑够健壮,编程的时候就只需要更多的去关注data的变化。思考问题的起点不同,效率和产出也不同。 6 | 7 | 8 | **怎么定义数据的变化?** 9 | 10 | 我们在用数据表述业务模型的时候,一般用到两种类型。一是single instance,二是collection type。single instance是单个的model实例。collection type是model实例的集合,array,set,dictionary都是属于集合类。这两者的基础变化离不开[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete),也就是我们常说的增删改查。把这四种行为定义成事件,再附带上变化的数据部分就可以描述“数据的改变”了。 11 | 12 | * **single instance**。在java当中,并没有语言层面的支持来检查model property的变化。不过property监控说到底还是观察者模式的实现,现在被广泛讨论的RxJava就是其中的典型代表。通过把property变成Observable,观察者作为Subscriber的方式,我们可以在Android上实现single instance的property监控。 13 | * **collection type**。java当中也没有办法直接表达collection type元素的变化,所以我们需要用RxJava来手动定义元素的CRUD,应用层只需要监听对应的事件即可。 14 | 15 | **RxJava** 16 | 17 | RxJava是ReactiveX在JVM上的实现,其核心是体现FRP的编程思想。Functional Programming和Reactive Programming近两年被讨论的很多,各种教程概念介绍也很多了。我个人感觉要完全实践这两种编程范式比较困难,但这两种范式里面所包涵的思想有很多可以借鉴的地方。Functional Programming里面对状态的immutable管理可以避免很多bug的产生,Reactive Programming里面的观察者模式以及Data Stream的概念可以让我们app的架构变得更整洁。不熟悉RxJava概念的朋友可以读下这个列表里[所涉及的文档](https://github.com/ReactiveX/RxJava/wiki/Additional-Reading)。 18 | 19 | **监听model property的变化** 20 | 21 | 22 | 23 | 上面代码当中描述的是如何监听person model当中的name property变化。name随时可能会变化,所以是被观察的角色,可以定义为Observable。property的监听方可以针对name添加新的Action。RxJava当中Observable在创建之后就无法emit新的数据了,这也符合Functional Programming的思想,要能持续的产生新的数据,就只能使用Subject(Subject既是被观察者也是订阅者),Subject也分好几种,BehaviorSubject能记住最后一次的最新数据,所以用来做property监控最合适。 24 | 25 | 基于model property变化所建立的观察者模型有一个重要前提,这个model在内存当中必须只有唯一一份拷贝。这个model的instance一旦被销毁,之前所建立的观察模型也就不成立了,重新生成的model instance也无法恢复之前的业务绑定。所以基于property的观察者模型必须建立持久的内存缓存。 26 | 27 | **监听model collection的变化** 28 | 29 | 因为model本身一般都会被持久化到database当中,所以model collection本身的变化可以从数据层发出。对于ORM类型数据库,每次model的插入,删除,或者修改都可通过一个事件通知到外部。用RxJava来描述会是这样: 30 | 31 | 32 | 33 | 这里我们使用了另一种Subject,PublishSubject和BehaviorSubject不同,不会纪录任何历史数据,订阅者之后收到订阅之后产生的新数据。 34 | 35 | ## **2.分层架构** 36 | 37 | 我们将DDA的架构分为三层: 38 | 39 | 40 | 41 | 这三层每一层都向下依赖,每一层之间通过面相接口编程的方式产生关联。 42 | 43 | ### **Application Layer** 44 | 45 | 这一层主要由我们熟悉的Activity和Fragment组成,工作职责包括采集用户数据和展示UI。采集数据是指数据从Application Layer流向Service Layer,展示UI是指观察Service Layer流入的数据变化并改变UI。可以假设这样一个业务场景来说明Application Layer的工作:用户在SettingActivity里改变自己的用户名。 46 | 47 | **数据的流出(采集数据)** 48 | 49 | 用户在SettingActivity的输入框里输入新的用户名,产生newName: String,newName需要传输到Server,收到成功回执之后再改变Activity当中的展示。这个完整的流程当中newName就是我们所关心的业务数据,在newName流向Service Layer之前我们可能需要进行校验(名字是否为空或超过了最大长度),这部分的逻辑更贴近界面的工作,且不涉及任何网络和DataBase操作,所以可以放在应用层。如果通过了校验,下一步就是将newName通过请求告诉Server,所有的网络和DataBase操作都发生在Service Layer,所以我们只需要将newName传输到Service Layer,到这一步就完成了数据的流出。 50 | 51 | **数据的流入(改变UI)** 52 | 53 | Application Layer将newName输出到Service Layer之后,接下来只需要作为观察者监控user: UserProfile这个model当中name property的变化。user model是一个viewModel,使用上和MVVM当中的ViewModel概念一致,ViewModel定义在应用层,但会通过事件观察者的方式绑定到Service Layer当中的RawModel。ViewModel负责把RawModel当中的数据转化成View所需要的样式,View在完成UI的配置之后就不需要维护其它的业务逻辑了。 54 | 55 | ### **Service Layer** 56 | 57 | Servicec Layer负责所有的网络请求实现,DataBase操作实现,以及一些公用的系统资源使用接口(比如GPS,相册权限,通讯录权限等)。对于Application Layer来说Service Layer就像是一个0ms延迟的Server,所有的服务都通过protocol的方式暴露给Application Layer。Service Layer和Data Access Layer(DAL)使用相同的RawModel定义,RawModel定义在DAL,从sqlite当中读出数据之后就会被马上转化成RawModel。RawModel不要和View进行直接绑定,通过ViewModel中转可以将数据改变的核心逻辑放在同一的地方管理,调试的时候会很有用。上面修改用户名的例子传入的newName,在这一层通过ModifyUserNameRequest通知Server。ModifyUserNameRequest成功回调之后将user model的name property修改为最新值。name一修改Application Layer对应的View立刻会收到数据改变的事件并展示新的name。Service Layer接下来需要把newName保存到数据库当中,涉及到和sqlite的交互。所有和sqlite直接打交道的工作都是交给Data Access Layer来做。 58 | 59 | ### **Data Access Layer(DAL)** 60 | 61 | DAL层对下负责和数据库直接交互,对上通过protocol的方式提供数据操作的接口给Service Layer。数据库我们使用sqlite。DAL层不涉及任何具体的业务逻辑,只提供基础的CRUD接口,这样一旦DAL层稳定下来,项目中后期出现业务bug基本就可以省去在DAL层调试。RawModel也定义在DAL,有些项目会在Service Layer和DAL各自定义自己的model,但每多一层model定义,就多了一次转换和维护的逻辑,对于大部分的项目来说其实没这个必要。DAL除了提供CRUD之外,还需要搭建线程模型,读写要分线程,而且需要同时提供同步异步两套接口。 62 | 63 | 这样初步进行职责划分后,我们可以得到一个细一点的层次图。 64 | 65 | 66 | 67 | 接下来是show the code,我们通过一个具体的demo例子来讨论DDA更多的细节。 68 | 69 | ## **3.Trim** 70 | 71 | Trim是用DDA架构搭建的一个Demo,其目标是将一些不同来源的数据以Feed流的方式展示在同一个界面当中,这样就不用打开n个不同的app去刷新查看了,这些数据可以来自任何有第三方开放平台的产品,比如微博,GitHub等。现阶段Demo已经实现的功能是拉取GitHub账户Repository的信息。这个功能已经包含完整的数据流,可以完整的体现DDA的思路。[点击GitHub地址](https://github.com/music4kid/Data-Driven-Architecture-Android)。 72 | 73 | Demo的目标是从GitHub上将某个账户所有的Repository拉取下来,形态如下: 74 | 75 | 76 | 77 | 先来看Feed对应的应用层逻辑,工程结构如下: 78 | 79 | 80 | 81 | 应用层主要是有Activity,Fragment及一些View组成。传统的做法是将布局交给layout xml去做,业务逻辑部分全部放到Activity或者Fragment,这种做法在后期业务增长之后会导致单个类文件的代码膨胀而难以维护。为了解决这个问题,可以使用MVVM,MVP这些设计模式来更合理的细化代码。我们这里采用MVP的方式,布局和UI相关的逻辑都放到Activity和Fragment,Presenter对用这里的BO(BusinessObject),另外我们在独立出一个类DH(Datahandler)来持有业务数据以及提供所有的数据操作接口。对于ListView或者RecyclerView使用Adapter能进一步将代码逻辑分散。 82 | 83 | 要实现数据驱动的设计,我们需要一种机制能将View的展示直接绑定到model上面。设计思路和MVVM类似,比如真对RecyclerView的每个ViewHolder我们都定义对应的ViewModel,ViewHolder里面的每个UI控件(比如TextView)都和ViewModel当中的property一一绑定。ViewModel还能处理一些业务数据的转换,以及事件的传递等。 84 | 85 | 代码细化分散之后要面对的一个问题是各个类文件之间的通信。为了降低和控制耦合度,对于BO,DH我们都定义对应的interface,各个类(比如ViewHolder)要将事件传递到BO,都需要通过对应的interface去访问。Android上可以通过依赖注入(比如roboguice)的方式来实现这种解耦。 86 | 87 | ### **Application Layer** 88 | 89 | Repository Cell里每个UI控件(4个TextView)与ViewModel property的绑定代码如下: 90 | 91 | 92 | 93 | repoName(TextView)的text直接和repo(ViewModel)的repoName(BehaviorSubject)绑定。考虑到ViewHolder的复用,需要在每次绑定的时候处理移除和重新添加的逻辑。 94 | 95 | ViewModel当中的property会进一步绑定到RawModel: 96 | 97 | 98 | 99 | 这里我们监听了来自data access layer的GitHubRepository update事件。每次update都会更新ViewModel当中的property,上面与之绑定的TextView的text也能立即得到更新。 100 | 101 | 监听GitHubRepository的update事件属于监听model collection的变化,上面我们还提到过可以直接监听model property的变化,这种监听方式要求model在内存缓存当中有唯一一份instance,绑定代码如下: 102 | 103 | 104 | 105 | 对于每个控件来说,ViewModel当中property的值都可以直接拿来使用,不需要做任何转换,转换的逻辑发生在ViewModel当中。绑定的代码虽然很简单,但observeOn里面其实需要处理更多的场景。 106 | 107 | * 绑定到新的property的时候,需要把旧的绑定解除(view重用的时候会有旧的绑定存在)。 108 | * 当控件被释放的时候,需要把绑定解除。 109 | * 绑定发生的时候需要针对初始值触发一次。 110 | * 绑定的添加和移除是线程安全的。 111 | 112 | FeedItemGitHubRepo作为ViewModel必须由RawModel来初始化,FeedItemGitHubRepo当中的每个property都需要一一对应绑定到RawModel的property当中,observeOn提供一个Action1的匿名类回调来添加额外的业务逻辑,可以做property值与类型的转换。这样每次Service Layer改变RawModel的任何property的时候,FeedItemGitHubRepo就能立刻收到事件并改变自身对应property的值,之后configCellWithItem当中的绑定也会被触发,UILabel等控件的值也随之更新。完成一个应用层的数据驱动链路。bindProperty添加的绑定与model本身的生命周期关联即可,因为不存在model被复用的情况。 113 | 114 | ### **Service Layer** 115 | 116 | 应用层的数据绑定确立以后,接下来是处理Service Layer的数据逻辑。RawModel的改变都发生在这一层,Application Layer不应该直接处理RawModel,所有的数据处理逻辑都应该通过Service Layer暴露接口来实现。Service Layer的结构如下: 117 | 118 | 119 | 120 | Service Layer的组织方式按照面向接口的方式,ServiceFactory是工厂类提供各种Service的实例。每种Service都有对应的interface定义其可供Application Layer使用的接口。GitHubServiceImp存放真正的实现。采用面向接口的方式除了和应用层解耦之外,还有另外两个好处: 121 | 122 | #### **Model Cache** 123 | 124 | 每个Service除了有Imp实现类之外,还可以定义一个Cache子类。GitHubServiceImp就对应了一个GitHubServiceCache 125 | 126 | ``` 127 | public class GitHubServiceCache extends GitHubServiceImp { 128 | private HashMap cachedRepoMap = new HashMap<>(); 129 | } 130 | ``` 131 | 132 | model的查询会频繁的发生在应用层,如果每次都从db里去获取,disk io带来的性能损耗必然会影响应用层的整体表现。所以对于业务频次高的模块需要建立对应的Model Cache。这个Cache对业务逻辑来说是透明的。比如加载所有repository的接口: 133 | 134 | 135 | 136 | 只要调用super.loadRepositories()就完成Imp当中的业务实现,cache的发生完全独立于业务逻辑。当然我们需要把ServiceFactory当中的实例替换成Cache对应的实例: 137 | 138 | 139 | 140 | 这里我们再次使用guice来做interface和implementation的绑定,可以看到IGitHubService绑定到了GitHubServiceCache。 141 | 142 | #### **Unit Test** 143 | 144 | 和Model Cache的添加方式类似,我们还可以针对每个Service的接口添加Unit Test。对于GitHubServiceImp来说,我们只需要添加一个GitHubServiceTest子类,再把需要测试的接口override并添加具体的测试逻辑即可。 145 | 146 | 比如我们想真对上面loadRepositories方法做一些测试,可以做如下实现: 147 | 148 | 149 | 150 | 同样测试的代码逻辑完全独立于具体的业务逻辑,和Cache的逻辑也没有任何关联。通过这种方式我们可以对每个关键的api都添加对应的Unit Test逻辑。当然ServiceFactory当中的实例也需要做替换: 151 | 152 | 153 | ``` 154 | bind(IGitHubService.class).to(GitHubServiceTest.class); 155 | ``` 156 | 157 | 所有的这些对Application Layer来说都是不可见,透明的。Application Layer使用的只是protocol当中定义的接口,对于具体是哪个Service的实例并不关心。 158 | 159 | #### **Unique Model Instance** 160 | 161 | 如果要做到ViewModel和RawModel之间property的一一绑定,关键的一点是要保证RawModel在Service Layer当中的唯一性。也就是说每个业务场景对应的RawModel在Service Layer只有一个实例对象。这样Application Layer不同的业务模块就能绑定到同一个model实例,model某个property变化的时候,各个业务模块都能收到相同的事件通知和数据。为Service实例添加Model Cache可以很好的保证Unique Model Instance。 162 | 163 | 如果RawModel没有存在Service Layer,ViewModel和RawModel之间的绑定就不成立,那么应用层需要通过另一种方式来监听数据的改变。 164 | 165 | ### **Data Access Layer(DAL)** 166 | 167 | DAL是与database(我们使用sqlite)直接打交道的部分。这一层是数据变化最可靠的源头,因为所有的数据只有持久化到database之后才算真正安全。如果无法建立ViewModel与RawModel的绑定,那么应用层就需要一种方式可以监听database数据的变化。依照CRUD原则,我们可以将这种变化定义为table row的增删改查,每一种row数据的变化都会触发一种对应事件。所以我们只需要定义一种数据结构同时描述table name和row change event即可。 168 | 169 | DAL工程结构如下: 170 | 171 | 172 | 173 | EventSource当中定义我们上面所说的collection数据变化,看下具体代码: 174 | 175 | 176 | 177 | 每个table对应一个DataEvent实例,每个DataEvent实例包含row change的事件,insert,delete,update对应增删改。 178 | 179 | 所以当我们从GitHub Server拉取到新的Repository的时候只需要对应触发insert事件,比如我们在DAL层保存新的Repository逻辑如下: 180 | 181 | 182 | 183 | 保存的时候会触发insert或者update事件。 184 | 185 | 应用层如果要监听新的插入数据,只需要注册绑定: 186 | 187 | 188 | 189 | 这样所有数据的变化都可以被应用层监听到。 190 | 191 | 这里DataEvent虽然对应用层可见,但这种model和事件的跨层是可以接受的,这种方式可以省去通过Service层中转的麻烦,而且DataEvent并不包含具体的业务逻辑,只描述数据最基础的变化,不会有业务依赖耦合带来的问题。 192 | 193 | 到这里可以看出Application Layer被驱动的方式有两种: 194 | 195 | * 被Service Layer的RawModel property变化驱动,这种驱动方式需要建立Model Cache,保证应用层的ViewModel都有一份对应的RawModel Cache。 196 | * 被DAL的DataEvent驱动,这种驱动方式更通用,不需要建立Cache,也可以被Service Layer监听。 197 | 198 | 一般场景下我们使用第二种数据驱动方式,在对性能要求较高的场景我们也可以使用第一种property binding的方式。 199 | 200 | 经过上面更细节的讨论后,我们总结下新的结构图: 201 | 202 | 203 | 204 | 更多的细节请查看代码。 205 | 206 | 207 | -------------------------------------------------------------------------------- /TrimData.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.mrpeak.trimdata" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | apply plugin: 'idea' 23 | idea { 24 | module { 25 | downloadJavadoc = true 26 | downloadSources = true 27 | } 28 | } 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | testCompile 'junit:junit:4.12' 33 | compile 'com.android.support:appcompat-v7:23.2.0' 34 | compile 'com.android.support:design:23.2.0' 35 | compile 'com.android.support:support-v4:23.2.0' 36 | compile 'org.roboguice:roboguice:3.0.1' 37 | compile 'com.google.code.findbugs:jsr305:1.3.9' 38 | compile 'com.android.support:recyclerview-v7:23.2.0' 39 | compile 'io.reactivex:rxjava:1.1.1' 40 | compile 'io.reactivex:rxandroid:1.1.0' 41 | compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' 42 | compile 'com.google.code.gson:gson:2.6.2' 43 | compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' 44 | compile 'com.squareup.okhttp3:okhttp:3.2.0' 45 | compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' 46 | } 47 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -ignorewarnings 2 | -dontoptimize 3 | -dontobfuscate 4 | -dontskipnonpubliclibraryclasses 5 | 6 | -ignorewarnings 7 | 8 | -renamesourcefileattribute SourceFile 9 | 10 | -keepattributes SourceFile,LineNumberTable,*Annotation* 11 | 12 | -printmapping map.txt 13 | -printseeds seed.txt 14 | 15 | -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } 16 | 17 | -keep public class * extends android.app.Activity 18 | -keep public class * extends android.app.Application 19 | -keep public class * extends android.app.Service 20 | -keep public class * extends android.content.BroadcastReceiver 21 | -keep public class * extends android.content.ContentProvider 22 | -keep public class * extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); public void set*(...); } 23 | 24 | -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } 25 | -keepclassmembers class android.support.v4.app.Fragment { *** getActivity(); public *** onCreate(); public *** onCreateOptionsMenu(...); } 26 | 27 | -keepclassmembers class * { @com.google.inject.Provides *; @android.test.suitebuilder.annotation.* *; void test*(...); } 28 | 29 | -keep public class roboguice.** 30 | -keep class com.google.inject.Binder 31 | -keep class com.google.inject.Key 32 | -keep class com.google.inject.Provider 33 | -keep class com.google.inject.TypeLiteral 34 | 35 | -keepclassmembers class * { @com.google.inject.Inject (...); } 36 | -keepclassmembers class com.google.inject.assistedinject.FactoryProvider2 { *; } 37 | -keepclassmembers class com.google.** { 38 | private void finalizeReferent(); 39 | protected void finalizeReferent(); 40 | public void finalizeReferent(); 41 | void finalizeReferent(); 42 | 43 | private *** startFinalizer(java.lang.Class,java.lang.Object); 44 | protected *** startFinalizer(java.lang.Class,java.lang.Object); 45 | public *** startFinalizer(java.lang.Class,java.lang.Object); 46 | *** startFinalizer(java.lang.Class,java.lang.Object); 47 | } 48 | 49 | -keep class com.github.pockethub.** 50 | -keepclassmembers class com.github.pockethub.** { *; } 51 | -keepclassmembers class com.github.pockethub.** { public (...); } 52 | -keep class org.eclipse.egit.github.** 53 | -keepclassmembers class com.github.pockethub.** { public (...); } 54 | -keepclassmembers class org.eclipse.egit.github.** { *; } 55 | 56 | -keepclassmembers class * extends com.actionbarsherlock.ActionBarSherlock { public (...); } 57 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mrpeak/trimdata/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata; 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 | 4 | 5 | 6 | 7 | 8 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/appIcon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/appIcon-web.png -------------------------------------------------------------------------------- /app/src/main/app_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/app_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/ppkit/android/ObservableUtil.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.ppkit.android; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | import rx.Observable; 7 | import rx.Subscription; 8 | import rx.android.schedulers.AndroidSchedulers; 9 | import rx.functions.Action1; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/3. 13 | */ 14 | public class ObservableUtil { 15 | private static HashMap> cachedSubscriptions = new HashMap<>(); 16 | 17 | public static void preserve(Subscription sub, Object token) { 18 | synchronized (cachedSubscriptions) { 19 | ArrayList subs = cachedSubscriptions.get(token); 20 | if (subs == null) { 21 | subs = new ArrayList<>(); 22 | cachedSubscriptions.put(token, subs); 23 | } 24 | subs.add(sub); 25 | } 26 | } 27 | 28 | public static void dispose(Object token) { 29 | synchronized (cachedSubscriptions) { 30 | ArrayList subs = cachedSubscriptions.get(token); 31 | if (subs == null) { 32 | 33 | } else { 34 | for (Subscription sub : subs) { 35 | sub.unsubscribe(); 36 | } 37 | subs.clear(); 38 | cachedSubscriptions.remove(token); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/MainTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | 13 | import com.mrpeak.trimdata.R; 14 | import com.mrpeak.trimdata.application.common.MyRoboActivity; 15 | import com.mrpeak.trimdata.application.feed.feedStream.FeedStreamFragment; 16 | import com.mrpeak.trimdata.application.setting.SettingListFragment; 17 | 18 | import roboguice.RoboGuice; 19 | 20 | /** 21 | * Created by gaofeng on 16/2/29. 22 | */ 23 | public class MainTabActivity extends MyRoboActivity { 24 | 25 | /** 26 | * The {@link android.support.v4.view.PagerAdapter} that will provide 27 | * fragments for each of the sections. We use a 28 | * {@link FragmentPagerAdapter} derivative, which will keep every 29 | * loaded fragment in memory. If this becomes too memory intensive, it 30 | * may be best to switch to a 31 | * {@link android.support.v4.app.FragmentStatePagerAdapter}. 32 | */ 33 | private SectionsPagerAdapter mSectionsPagerAdapter; 34 | 35 | /** 36 | * The {@link ViewPager} that will host the section contents. 37 | */ 38 | private ViewPager mViewPager; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_main); 44 | 45 | RoboGuice.setUseAnnotationDatabases(false); 46 | 47 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 48 | setSupportActionBar(toolbar); 49 | // Create the adapter that will return a fragment for each of the three 50 | // primary sections of the activity. 51 | mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); 52 | 53 | // Set up the ViewPager with the sections adapter. 54 | mViewPager = (ViewPager) findViewById(R.id.container); 55 | mViewPager.setAdapter(mSectionsPagerAdapter); 56 | 57 | TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); 58 | tabLayout.setupWithViewPager(mViewPager); 59 | } 60 | 61 | 62 | @Override 63 | public boolean onCreateOptionsMenu(Menu menu) { 64 | // Inflate the menu; this adds items to the action bar if it is present. 65 | getMenuInflater().inflate(R.menu.menu_main, menu); 66 | return true; 67 | } 68 | 69 | @Override 70 | public boolean onOptionsItemSelected(MenuItem item) { 71 | // Handle action bar item clicks here. The action bar will 72 | // automatically handle clicks on the Home/Up button, so long 73 | // as you specify a parent activity in AndroidManifest.xml. 74 | int id = item.getItemId(); 75 | 76 | //noinspection SimplifiableIfStatement 77 | if (id == R.id.action_settings) { 78 | return true; 79 | } 80 | 81 | return super.onOptionsItemSelected(item); 82 | } 83 | 84 | 85 | 86 | 87 | /** 88 | * A {@link FragmentPagerAdapter} that returns a fragment corresponding to 89 | * one of the sections/tabs/pages. 90 | */ 91 | public class SectionsPagerAdapter extends FragmentPagerAdapter { 92 | 93 | public SectionsPagerAdapter(FragmentManager fm) { 94 | super(fm); 95 | } 96 | 97 | @Override 98 | public Fragment getItem(int position) { 99 | // getItem is called to instantiate the fragment for the given page. 100 | // Return a PlaceholderFragment (defined as a static inner class below). 101 | Fragment f; 102 | if (position == 0) { 103 | f = new FeedStreamFragment(); 104 | } else { 105 | f = new SettingListFragment(); 106 | } 107 | return f; 108 | } 109 | 110 | @Override 111 | public int getCount() { 112 | return 2; 113 | } 114 | 115 | @Override 116 | public CharSequence getPageTitle(int position) { 117 | switch (position) { 118 | case 0: 119 | return "Feed"; 120 | case 1: 121 | return "Setting"; 122 | } 123 | return null; 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import roboguice.RoboGuice; 7 | import roboguice.activity.RoboActivity; 8 | 9 | public class BaseActivity extends RoboActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | 15 | RoboGuice.setUseAnnotationDatabases(false); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseBusinessObject.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.content.Context; 4 | 5 | import rx.subscriptions.CompositeSubscription; 6 | 7 | /** 8 | * Created by gaofeng on 16/3/2. 9 | */ 10 | public class BaseBusinessObject { 11 | public Object dh; 12 | public Context mContext; 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseDataHandler.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by gaofeng on 16/3/2. 7 | */ 8 | public class BaseDataHandler { 9 | public Context mContext; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.mrpeak.trimdata.R; 12 | 13 | import roboguice.fragment.RoboFragment; 14 | import rx.subscriptions.CompositeSubscription; 15 | 16 | /** 17 | * A simple {@link Fragment} subclass. 18 | */ 19 | public class BaseFragment extends RoboFragment { 20 | 21 | public BaseFragment() { 22 | } 23 | 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 26 | Bundle savedInstanceState) { 27 | TextView textView = new TextView(getActivity()); 28 | textView.setText(R.string.hello_blank_fragment); 29 | return textView; 30 | } 31 | 32 | @Override 33 | public void onDestroy() { 34 | super.onDestroy(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.ViewGroup; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/2. 10 | */ 11 | public class BaseRecyclerViewAdapter extends RecyclerView.Adapter { 12 | 13 | public Object bo = null; 14 | 15 | @Override 16 | public long getItemId(int position) { 17 | return position; 18 | } 19 | 20 | @Override 21 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { 22 | super.onBindViewHolder(holder, position, payloads); 23 | } 24 | 25 | @Override 26 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 27 | return null; 28 | } 29 | 30 | @Override 31 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 32 | 33 | } 34 | 35 | @Override 36 | public int getItemCount() { 37 | return 0; 38 | } 39 | 40 | @Override 41 | public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { 42 | super.onViewDetachedFromWindow(holder); 43 | 44 | if (holder instanceof BaseViewHolder) { 45 | BaseViewHolder vh = (BaseViewHolder)holder; 46 | vh.onDetachFromWindow(); 47 | } 48 | } 49 | 50 | @Override 51 | public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { 52 | super.onViewAttachedToWindow(holder); 53 | 54 | if (holder instanceof BaseViewHolder) { 55 | BaseViewHolder vh = (BaseViewHolder)holder; 56 | vh.onAttachToWindow(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/BaseViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by gaofeng on 16/3/2. 8 | */ 9 | public class BaseViewHolder extends RecyclerView.ViewHolder { 10 | public Object bo = null; 11 | 12 | public BaseViewHolder(View itemView) { 13 | super(itemView); 14 | 15 | itemView.setOnClickListener(new View.OnClickListener() { 16 | @Override 17 | public void onClick(View v) { 18 | onViewHolderClick(); 19 | } 20 | }); 21 | } 22 | 23 | public void onDetachFromWindow() { 24 | } 25 | public void onAttachToWindow() { 26 | } 27 | public void onViewHolderClick() { 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/ItemListFragment.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.mrpeak.trimdata.R; 13 | 14 | import roboguice.fragment.RoboFragment; 15 | 16 | public class ItemListFragment extends BaseFragment { 17 | 18 | protected SwipeRefreshLayout mSwipeRefreshLayout = null; 19 | protected RecyclerView mRecyclerView = null; 20 | 21 | public ItemListFragment() { 22 | } 23 | 24 | @SuppressWarnings("unused") 25 | public static ItemListFragment newInstance() { 26 | ItemListFragment fragment = new ItemListFragment(); 27 | return fragment; 28 | } 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 37 | Bundle savedInstanceState) { 38 | View view = inflater.inflate(R.layout.fragment_itemlist_list, container, false); 39 | //setup recycler view 40 | View subView = view.findViewById(R.id.recyclerList); 41 | if (subView instanceof RecyclerView) { 42 | Context context = view.getContext(); 43 | mRecyclerView = (RecyclerView) subView; 44 | mRecyclerView.setLayoutManager(new LinearLayoutManager(context)); 45 | } 46 | //setup refresh view 47 | mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.fragment_itemlist_swipeRefreshLayout); 48 | if (mSwipeRefreshLayout != null) { 49 | mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 50 | @Override 51 | public void onRefresh() { 52 | ItemListFragment.this.onRefreshTriggered(); 53 | } 54 | }); 55 | } 56 | 57 | return view; 58 | } 59 | 60 | 61 | @Override 62 | public void onAttach(Context context) { 63 | super.onAttach(context); 64 | } 65 | 66 | @Override 67 | public void onDetach() { 68 | super.onDetach(); 69 | } 70 | 71 | public void setRecyclerViewAdapter(RecyclerView.Adapter adapter) { 72 | if (mRecyclerView != null) { 73 | mRecyclerView.setAdapter(adapter); 74 | } 75 | } 76 | 77 | protected void onRefreshTriggered() { 78 | 79 | } 80 | 81 | protected void stopRefreshing() { 82 | if (mSwipeRefreshLayout != null) { 83 | mSwipeRefreshLayout.setRefreshing(false); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/MyRoboActivity.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | import com.google.inject.Key; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import roboguice.RoboGuice; 12 | import roboguice.event.EventManager; 13 | import roboguice.inject.RoboInjector; 14 | import roboguice.util.RoboContext; 15 | 16 | public class MyRoboActivity extends AppCompatActivity implements RoboContext { 17 | 18 | protected HashMap, Object> scopedObjects = new HashMap<>(); 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | // final RoboInjector injector = RoboGuice.getInjector(this); 23 | // injector.injectMembersWithoutViews( this ); 24 | super.onCreate(savedInstanceState); 25 | 26 | RoboGuice.setUseAnnotationDatabases(false); 27 | } 28 | 29 | @Override 30 | public Map, Object> getScopedObjectMap() { 31 | return scopedObjects; 32 | } 33 | 34 | @Override 35 | protected void onDestroy() { 36 | try { 37 | } 38 | finally { 39 | try { 40 | RoboGuice.destroyInjector( this ); 41 | } 42 | finally { 43 | super.onDestroy(); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/common/MyRoboFragment.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.common; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.annotation.CallSuper; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.mrpeak.trimdata.R; 13 | 14 | import roboguice.RoboGuice; 15 | 16 | /** 17 | * A simple {@link Fragment} subclass. 18 | */ 19 | public class MyRoboFragment extends Fragment { 20 | 21 | @Override 22 | @CallSuper 23 | public void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | RoboGuice.getInjector(getActivity()).injectMembersWithoutViews(this); 26 | } 27 | 28 | @Override 29 | @CallSuper 30 | public void onViewCreated(View view, Bundle savedInstanceState) { 31 | super.onViewCreated(view, savedInstanceState); 32 | RoboGuice.getInjector(getActivity()).injectViewMembers(this); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/FeedDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | import com.google.inject.Inject; 12 | import com.mrpeak.ppkit.android.ObservableUtil; 13 | import com.mrpeak.trimdata.R; 14 | import com.mrpeak.trimdata.application.common.BaseActivity; 15 | import com.mrpeak.trimdata.application.feed.feedDetail.model.FeedDetailGitHubRepo; 16 | import com.mrpeak.trimdata.application.feed.feedStream.IFeedStreamBO; 17 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItemGitHubRepo; 18 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 19 | import com.mrpeak.trimdata.service.ServiceFactory; 20 | 21 | import org.w3c.dom.Text; 22 | 23 | import roboguice.inject.InjectView; 24 | import rx.Subscription; 25 | import rx.functions.Action1; 26 | 27 | public class FeedDetailActivity extends BaseActivity { 28 | 29 | public static final String FEED_DETAIL_ID = "feedDetailId"; 30 | 31 | @Inject private FeedDetailBO bo; 32 | @Inject private FeedDetailDH dh; 33 | 34 | @InjectView(R.id.toolbar) Toolbar toolbar; 35 | @InjectView(R.id.txtRepoName) TextView txtRepoName; 36 | @InjectView(R.id.txtRepoStar) TextView txtRepoStar; 37 | @InjectView(R.id.txtRepoFork) TextView txtRepoFork; 38 | @InjectView(R.id.txtRepoOpenIssue) TextView txtRepoOpenIssue; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_feed_detail); 44 | 45 | Long itemId = getIntent().getLongExtra(FEED_DETAIL_ID, -1); 46 | if (itemId != -1) { 47 | GitHubRepository repo = ServiceFactory.getInstance().gitHubService.getRepositoryById(itemId); 48 | if (repo != null) { 49 | FeedDetailGitHubRepo item = new FeedDetailGitHubRepo(repo); 50 | dh.setFeedDetailItem(item); 51 | 52 | toolbar.setTitle(item.getRepoName().getValue()); 53 | 54 | //do UI binding 55 | Subscription s; 56 | s = item.getRepoName().subscribe(new Action1() { 57 | @Override 58 | public void call(String s) { 59 | txtRepoName.setText(s); 60 | } 61 | }); 62 | ObservableUtil.preserve(s, this); 63 | 64 | s = item.getRepoStar().subscribe(new Action1() { 65 | @Override 66 | public void call(String s) { 67 | txtRepoStar.setText(s); 68 | } 69 | }); 70 | ObservableUtil.preserve(s, this); 71 | 72 | s = item.getRepoFork().subscribe(new Action1() { 73 | @Override 74 | public void call(String s) { 75 | txtRepoFork.setText(s); 76 | } 77 | }); 78 | ObservableUtil.preserve(s, this); 79 | 80 | s = item.getRepoOpenIssue().subscribe(new Action1() { 81 | @Override 82 | public void call(String s) { 83 | txtRepoOpenIssue.setText(s); 84 | } 85 | }); 86 | ObservableUtil.preserve(s, this); 87 | } 88 | } 89 | 90 | } 91 | 92 | @Override 93 | protected void onDestroy() { 94 | super.onDestroy(); 95 | ObservableUtil.dispose(this); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/FeedDetailBO.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.inject.Inject; 6 | import com.mrpeak.trimdata.application.common.BaseBusinessObject; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/11. 10 | */ 11 | public class FeedDetailBO extends BaseBusinessObject implements IFeedDetailBO { 12 | 13 | @Inject 14 | public FeedDetailBO(Context mContext) { 15 | this.mContext = mContext; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/FeedDetailDH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.IntDef; 5 | 6 | import com.google.inject.Inject; 7 | import com.mrpeak.trimdata.application.common.BaseDataHandler; 8 | import com.mrpeak.trimdata.application.feed.feedDetail.model.FeedDetailGitHubRepo; 9 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItemGitHubRepo; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/11. 13 | */ 14 | public class FeedDetailDH extends BaseDataHandler implements IFeedDetailDH { 15 | private FeedDetailGitHubRepo mRepo; 16 | 17 | @Inject 18 | public FeedDetailDH(Context context) { 19 | mContext = context; 20 | } 21 | 22 | @Override 23 | public void setFeedDetailItem(FeedDetailGitHubRepo repo) { 24 | mRepo = repo; 25 | } 26 | 27 | @Override 28 | public FeedDetailGitHubRepo getFeedDetailItem() { 29 | return mRepo; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/IFeedDetailBO.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/11. 5 | */ 6 | public interface IFeedDetailBO { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/IFeedDetailDH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail; 2 | 3 | import com.mrpeak.trimdata.application.feed.feedDetail.model.FeedDetailGitHubRepo; 4 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 5 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItemGitHubRepo; 6 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/11. 10 | */ 11 | public interface IFeedDetailDH { 12 | void setFeedDetailItem(FeedDetailGitHubRepo repo); 13 | FeedDetailGitHubRepo getFeedDetailItem(); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/model/FeedDetail.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail.model; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/11. 5 | */ 6 | public class FeedDetail { 7 | public static final int FeedItemNormal = 0; 8 | public static final int FeedItemGitHubRepo = 1; 9 | public static final int FeedItemUnknown = 1000; 10 | 11 | private int itemType; 12 | private Long itemId; 13 | 14 | public FeedDetail(int itemType, Long itemId) { 15 | this.itemType = itemType; 16 | this.itemId = itemId; 17 | } 18 | 19 | public int getItemType() { 20 | return itemType; 21 | } 22 | 23 | public Long getItemId() { 24 | return itemId; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedDetail/model/FeedDetailGitHubRepo.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedDetail.model; 2 | 3 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 4 | import com.mrpeak.trimdata.dal.EventSource; 5 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 6 | 7 | import rx.android.schedulers.AndroidSchedulers; 8 | import rx.functions.Action1; 9 | import rx.subjects.BehaviorSubject; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/11. 13 | */ 14 | public class FeedDetailGitHubRepo extends FeedDetail { 15 | public BehaviorSubject repoName; 16 | public BehaviorSubject repoStar; 17 | public BehaviorSubject repoFork; 18 | public BehaviorSubject repoOpenIssue; 19 | 20 | public FeedDetailGitHubRepo(final GitHubRepository repo) { 21 | super(FeedItemGitHubRepo, repo.getRepoId().getValue()); 22 | this.repoName = BehaviorSubject.create(""); 23 | this.repoStar = BehaviorSubject.create(""); 24 | this.repoFork = BehaviorSubject.create(""); 25 | this.repoOpenIssue = BehaviorSubject.create(""); 26 | 27 | //option2: subscribe to model instance 28 | updateFeedDetail(repo); 29 | EventSource.getInstance().githubEvent.update 30 | .observeOn(AndroidSchedulers.mainThread()) 31 | .subscribe(new Action1() { 32 | @Override 33 | public void call(GitHubRepository repo) { 34 | updateFeedDetail(repo); 35 | } 36 | }); 37 | } 38 | 39 | private void updateFeedDetail(GitHubRepository repo) { 40 | if (repo.getRepoId().getValue() == getItemId()) { 41 | getRepoName().onNext("repository name:" + repo.getName().getValue()); 42 | getRepoStar().onNext("total star:" + repo.getStar().getValue()); 43 | getRepoFork().onNext("total fork:" + repo.getFork().getValue()); 44 | getRepoOpenIssue().onNext("total openIssue:" + repo.getOpenIssue().getValue()); 45 | } 46 | } 47 | 48 | public BehaviorSubject getRepoName() { 49 | return repoName; 50 | } 51 | 52 | public BehaviorSubject getRepoStar() { 53 | return repoStar; 54 | } 55 | 56 | public BehaviorSubject getRepoFork() { 57 | return repoFork; 58 | } 59 | 60 | public BehaviorSubject getRepoOpenIssue() { 61 | return repoOpenIssue; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/FeedStreamAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.google.inject.Inject; 10 | import com.mrpeak.trimdata.R; 11 | import com.mrpeak.trimdata.application.common.BaseRecyclerViewAdapter; 12 | import com.mrpeak.trimdata.application.common.BaseViewHolder; 13 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 14 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItemGitHubRepo; 15 | import com.mrpeak.trimdata.application.feed.feedStream.view.FeedItemGitHubRepoVH; 16 | import com.mrpeak.trimdata.application.feed.feedStream.view.FeedItemVH; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by gaofeng on 16/3/2. 23 | */ 24 | public class FeedStreamAdapter extends BaseRecyclerViewAdapter { 25 | private ArrayList mItems; 26 | 27 | private final Context mContext; 28 | private final LayoutInflater mInflator; 29 | 30 | @Inject 31 | public FeedStreamAdapter(final Context context) { 32 | 33 | mContext = context; 34 | mInflator = LayoutInflater.from(context); 35 | 36 | setHasStableIds(true); 37 | } 38 | 39 | @Override 40 | public long getItemId(int position) { 41 | FeedItem item = mItems.get(position); 42 | long itemId = item.getItemId(); 43 | return itemId; 44 | } 45 | 46 | @Override 47 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 48 | BaseViewHolder vh = null; 49 | if (viewType == FeedItem.FeedItemGitHubRepo) { 50 | View view = mInflator.inflate(R.layout.fragment_itemlist_github_repo, parent, false); 51 | vh = new FeedItemGitHubRepoVH(view); 52 | } 53 | 54 | if (vh != null) { 55 | vh.bo = bo; 56 | } 57 | return vh; 58 | } 59 | 60 | @Override 61 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 62 | if (holder instanceof FeedItemVH) { 63 | FeedItemVH vh = (FeedItemVH)holder; 64 | FeedItem item = mItems.get(position); 65 | vh.bindFeedItem(item); 66 | } 67 | } 68 | 69 | @Override 70 | public int getItemViewType(int position) { 71 | return getFeedItemType(position); 72 | } 73 | 74 | @Override 75 | public int getItemCount() { 76 | int count = mItems.size(); 77 | return count; 78 | } 79 | 80 | private int getFeedItemType(int position) { 81 | if (position > mItems.size() - 1) { 82 | return FeedItem.FeedItemUnknown; 83 | } 84 | FeedItem item = mItems.get(position); 85 | return item.getItemType(); 86 | } 87 | 88 | public List getmItems() { 89 | return mItems; 90 | } 91 | 92 | public void setmItems(ArrayList mItems) { 93 | this.mItems = mItems; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/FeedStreamBO.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.support.v4.app.FragmentTransaction; 6 | import android.util.Log; 7 | import android.widget.Toast; 8 | 9 | import com.google.inject.Inject; 10 | import com.mrpeak.trimdata.application.common.BaseBusinessObject; 11 | import com.mrpeak.trimdata.application.feed.feedDetail.FeedDetailActivity; 12 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItemGitHubRepo; 13 | import com.mrpeak.trimdata.dal.EventSource; 14 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 15 | import com.mrpeak.trimdata.service.ServiceFactory; 16 | import com.mrpeak.trimdata.service.network.PPHttpRequest; 17 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 18 | import com.mrpeak.trimdata.service.network.github.GitHubRepositoryListRequest; 19 | 20 | import java.util.ArrayList; 21 | 22 | import rx.functions.Action1; 23 | 24 | /** 25 | * Created by gaofeng on 16/2/29. 26 | */ 27 | public class FeedStreamBO extends BaseBusinessObject implements IFeedStreamBO, 28 | PPHttpRequestDelegate, 29 | GitHubRepositoryListRequest.GitHubRepositoryListRequestDelegate { 30 | private final Context context; 31 | public FeedStreamBOListener mListener = null; 32 | 33 | @Inject 34 | public FeedStreamBO(final Context context) { 35 | this.context = context; 36 | 37 | //bind data event 38 | EventSource.getInstance().githubEvent.delete.subscribe(new Action1() { 39 | @Override 40 | public void call(GitHubRepository repository) { 41 | //delete repo and refresh recycler view 42 | Log.d("", "github item deleted"); 43 | } 44 | }); 45 | } 46 | 47 | public void loadFeedsFromServer() { 48 | ServiceFactory.getInstance().gitHubService.loadAllRepositoriesFromServer(this); 49 | } 50 | 51 | public void loadFeedsFromDisk() { 52 | ArrayList repos = ServiceFactory.getInstance().gitHubService.loadAllRepositories(); 53 | ArrayList items = new ArrayList<>(); 54 | 55 | for (int i = 0; i < repos.size(); i ++) { 56 | GitHubRepository repo = repos.get(i); 57 | FeedItemGitHubRepo item = new FeedItemGitHubRepo(repo); 58 | 59 | items.add(item); 60 | } 61 | 62 | if (dh instanceof IFeedStreamDH) { 63 | IFeedStreamDH handler = (IFeedStreamDH) dh; 64 | handler.setmItems(items); 65 | } 66 | if (mListener != null) { 67 | mListener.loadAllRepoFinished(); 68 | } 69 | } 70 | 71 | @Override 72 | public void gotoFeedDetail(Long feedItemId) { 73 | Intent intent = new Intent(mContext, FeedDetailActivity.class); 74 | intent.putExtra(FeedDetailActivity.FEED_DETAIL_ID, feedItemId); 75 | mContext.startActivity(intent); 76 | } 77 | 78 | //region Http Request Callback 79 | @Override 80 | public void onRequestSuccess(PPHttpRequest req, int code) { 81 | 82 | } 83 | 84 | @Override 85 | public void onRequestFail(String msg) { 86 | Toast.makeText(mContext, "get repositories failed, try again later", Toast.LENGTH_SHORT).show(); 87 | if (mListener != null) { 88 | mListener.loadAllRepoFailed(); 89 | } 90 | } 91 | //endregion 92 | 93 | @Override 94 | public void getGitHubRepoFinished(ArrayList repos) { 95 | ArrayList items = new ArrayList<>(); 96 | for (GitHubRepository repo : repos) { 97 | FeedItemGitHubRepo item = new FeedItemGitHubRepo(repo); 98 | items.add(item); 99 | } 100 | 101 | if (dh instanceof IFeedStreamDH) { 102 | IFeedStreamDH handler = (IFeedStreamDH) dh; 103 | handler.setmItems(items); 104 | } 105 | if (mListener != null) { 106 | mListener.loadAllRepoFinished(); 107 | } 108 | } 109 | 110 | 111 | 112 | public interface FeedStreamBOListener { 113 | void loadAllRepoFinished(); 114 | void loadAllRepoFailed(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/FeedStreamDH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.inject.Inject; 6 | import com.mrpeak.trimdata.application.common.BaseDataHandler; 7 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 8 | 9 | import java.util.ArrayList; 10 | 11 | /** 12 | * Created by gaofeng on 16/2/29. 13 | */ 14 | public class FeedStreamDH extends BaseDataHandler implements IFeedStreamDH { 15 | private final Context context; 16 | 17 | private ArrayList mItems = new ArrayList<>(); 18 | 19 | @Inject 20 | public FeedStreamDH(final Context context) { 21 | this.context = context; 22 | } 23 | 24 | public ArrayList getmItems() { 25 | return mItems; 26 | } 27 | public void setmItems(ArrayList items) { 28 | this.mItems.clear(); 29 | for (FeedItem item : items) { 30 | this.mItems.add(item); 31 | } 32 | } 33 | 34 | public void addItem(T item) { 35 | this.mItems.add(item); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/FeedStreamFragment.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.UiThread; 6 | import android.view.View; 7 | 8 | import com.google.inject.Inject; 9 | import com.mrpeak.trimdata.application.common.ItemListFragment; 10 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 11 | 12 | public class FeedStreamFragment extends ItemListFragment implements FeedStreamBO.FeedStreamBOListener { 13 | 14 | @Inject 15 | private FeedStreamBO bo; 16 | @Inject 17 | public FeedStreamDH dh; 18 | @Inject 19 | private FeedStreamAdapter adapter; 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | 25 | //do initialization 26 | bo.mContext = getContext(); 27 | bo.dh = dh; 28 | bo.mListener = this; 29 | 30 | adapter.bo = bo; 31 | adapter.setmItems(dh.getmItems()); 32 | 33 | } 34 | 35 | @Override 36 | public void onViewCreated(View view, Bundle savedInstanceState) { 37 | super.onViewCreated(view, savedInstanceState); 38 | 39 | setRecyclerViewAdapter(adapter); 40 | 41 | bo.loadFeedsFromDisk(); 42 | 43 | bo.loadFeedsFromServer(); 44 | } 45 | 46 | @Override 47 | public void onAttach(Context context) { 48 | super.onAttach(context); 49 | 50 | } 51 | 52 | @Override 53 | public void onDetach() { 54 | super.onDetach(); 55 | } 56 | 57 | @Override 58 | @UiThread 59 | public void loadAllRepoFinished() { 60 | adapter.notifyDataSetChanged(); 61 | stopRefreshing(); 62 | } 63 | 64 | @Override 65 | @UiThread 66 | public void loadAllRepoFailed() { 67 | stopRefreshing(); 68 | } 69 | 70 | @Override 71 | protected void onRefreshTriggered() { 72 | super.onRefreshTriggered(); 73 | 74 | bo.loadFeedsFromServer(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/IFeedStreamBO.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/2. 5 | */ 6 | public interface IFeedStreamBO { 7 | void loadFeedsFromServer(); 8 | void gotoFeedDetail(Long feedItemId); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/IFeedStreamDH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream; 2 | 3 | import com.mrpeak.trimdata.application.feed.feedStream.model.FeedItem; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Created by gaofeng on 16/3/2. 9 | */ 10 | public interface IFeedStreamDH { 11 | ArrayList getmItems(); 12 | void setmItems(ArrayList items); 13 | void addItem(T item); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/model/FeedItem.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream.model; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/1. 5 | */ 6 | 7 | 8 | public class FeedItem { 9 | public static final int FeedItemNormal = 0; 10 | public static final int FeedItemGitHubRepo = 1; 11 | public static final int FeedItemUnknown = 1000; 12 | 13 | private int itemType; 14 | private Long itemId; 15 | private int displayHeight; 16 | 17 | public FeedItem(int itemType, Long itemId) { 18 | this.itemType = itemType; 19 | this.itemId = itemId; 20 | } 21 | 22 | public int getItemType() { 23 | return itemType; 24 | } 25 | 26 | public Long getItemId() { 27 | return itemId; 28 | } 29 | 30 | public int getDisplayHeight() { 31 | return displayHeight; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/model/FeedItemGitHubRepo.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream.model; 2 | 3 | import com.mrpeak.trimdata.dal.EventSource; 4 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 5 | 6 | import rx.android.schedulers.AndroidSchedulers; 7 | import rx.functions.Action1; 8 | import rx.subjects.BehaviorSubject; 9 | 10 | /** 11 | * Created by gaofeng on 16/3/1. 12 | */ 13 | public class FeedItemGitHubRepo extends FeedItem { 14 | public BehaviorSubject repoName; 15 | public BehaviorSubject repoStar; 16 | public BehaviorSubject repoFork; 17 | public BehaviorSubject repoOpenIssue; 18 | 19 | public FeedItemGitHubRepo(final GitHubRepository repo) { 20 | super(FeedItem.FeedItemGitHubRepo, repo.getRepoId().getValue()); 21 | this.repoName = BehaviorSubject.create(""); 22 | this.repoStar = BehaviorSubject.create(""); 23 | this.repoFork = BehaviorSubject.create(""); 24 | this.repoOpenIssue = BehaviorSubject.create(""); 25 | 26 | //option1: subscribe to property, dependent on permanent memory cache 27 | // repo.getName().subscribe(new Action1() { 28 | // @Override 29 | // public void call(String s) { 30 | // FeedItemGitHubRepo.this.getRepoName().onNext(repo.getName().getValue()); 31 | // } 32 | // }); 33 | // 34 | // repo.getStar().subscribe(new Action1() { 35 | // @Override 36 | // public void call(Integer integer) { 37 | // FeedItemGitHubRepo.this.getRepoStar().onNext("star:" + repo.getStar().getValue()); 38 | // } 39 | // }); 40 | // 41 | // repo.getFork().subscribe(new Action1() { 42 | // @Override 43 | // public void call(Integer integer) { 44 | // FeedItemGitHubRepo.this.getRepoFork().onNext("fork:" + repo.getFork().getValue()); 45 | // } 46 | // }); 47 | // 48 | // repo.getOpenIssue().subscribe(new Action1() { 49 | // @Override 50 | // public void call(Integer integer) { 51 | // FeedItemGitHubRepo.this.getRepoOpenIssue().onNext("openIssue:" + repo.getOpenIssue().getValue()); 52 | // } 53 | // }); 54 | 55 | 56 | //option2: subscribe to model instance 57 | updateFeedItemGitHubRepo(repo); 58 | EventSource.getInstance().githubEvent.update 59 | .observeOn(AndroidSchedulers.mainThread()) 60 | .subscribe(new Action1() { 61 | @Override 62 | public void call(GitHubRepository repo) { 63 | updateFeedItemGitHubRepo(repo); 64 | } 65 | }); 66 | } 67 | 68 | private void updateFeedItemGitHubRepo(GitHubRepository repo) { 69 | if (repo.getRepoId().getValue() == getItemId()) { 70 | getRepoName().onNext(repo.getName().getValue()); 71 | getRepoStar().onNext("star:" + repo.getStar().getValue()); 72 | getRepoFork().onNext("fork:" + repo.getFork().getValue()); 73 | getRepoOpenIssue().onNext("openIssue:" + repo.getOpenIssue().getValue()); 74 | } 75 | } 76 | 77 | 78 | public BehaviorSubject getRepoName() { 79 | return repoName; 80 | } 81 | 82 | public BehaviorSubject getRepoStar() { 83 | return repoStar; 84 | } 85 | 86 | public BehaviorSubject getRepoFork() { 87 | return repoFork; 88 | } 89 | 90 | public BehaviorSubject getRepoOpenIssue() { 91 | return repoOpenIssue; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/view/FeedItemGitHubRepoVH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream.view; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import com.mrpeak.ppkit.android.ObservableUtil; 7 | import com.mrpeak.trimdata.R; 8 | import com.mrpeak.trimdata.application.feed.feedStream.IFeedStreamBO; 9 | import com.mrpeak.trimdata.application.feed.feedStream.model.*; 10 | 11 | import rx.Subscription; 12 | import rx.android.schedulers.AndroidSchedulers; 13 | import rx.functions.Action1; 14 | 15 | /** 16 | * Created by gaofeng on 16/3/2. 17 | */ 18 | 19 | public class FeedItemGitHubRepoVH extends FeedItemVH { 20 | private TextView repoName; 21 | private TextView repoStar; 22 | private TextView repoFork; 23 | private TextView repoOpenIssue; 24 | 25 | private FeedItemGitHubRepo repo; 26 | 27 | public FeedItemGitHubRepoVH(View itemView) { 28 | super(itemView); 29 | repoName = (TextView) itemView.findViewById(R.id.txtRepoName); 30 | repoStar = (TextView) itemView.findViewById(R.id.txtRepoStar); 31 | repoFork = (TextView) itemView.findViewById(R.id.txtRepoFork); 32 | repoOpenIssue = (TextView) itemView.findViewById(R.id.txtRepoOpenIssue); 33 | } 34 | 35 | @Override 36 | public void onViewHolderClick() { 37 | super.onViewHolderClick(); 38 | 39 | if (bo instanceof IFeedStreamBO) { 40 | IFeedStreamBO ibo = (IFeedStreamBO)bo; 41 | ibo.gotoFeedDetail(repo.getItemId()); 42 | } 43 | } 44 | 45 | @Override 46 | public void bindFeedItem(FeedItem item) { 47 | 48 | repo = (FeedItemGitHubRepo)item; 49 | 50 | //remove old binding first 51 | ObservableUtil.dispose(this); 52 | 53 | //create new binding 54 | Subscription s; 55 | 56 | s = repo.getRepoName().observeOn(AndroidSchedulers.mainThread()) 57 | .subscribe(new Action1() { 58 | @Override 59 | public void call(String s) { 60 | repoName.setText(s); 61 | } 62 | }); 63 | ObservableUtil.preserve(s, this); 64 | 65 | s = repo.getRepoStar().observeOn(AndroidSchedulers.mainThread()) 66 | .subscribe(new Action1() { 67 | @Override 68 | public void call(String s) { 69 | repoStar.setText(s); 70 | } 71 | }); 72 | ObservableUtil.preserve(s, this); 73 | 74 | repoStar.setOnClickListener(new View.OnClickListener() { 75 | @Override 76 | public void onClick(View v) { 77 | 78 | } 79 | }); 80 | 81 | s = repo.getRepoFork().observeOn(AndroidSchedulers.mainThread()) 82 | .subscribe(new Action1() { 83 | @Override 84 | public void call(String s) { 85 | repoFork.setText(s); 86 | } 87 | }); 88 | ObservableUtil.preserve(s, this); 89 | 90 | s = repo.getRepoOpenIssue().observeOn(AndroidSchedulers.mainThread()) 91 | .subscribe(new Action1() { 92 | @Override 93 | public void call(String s) { 94 | repoOpenIssue.setText(s); 95 | } 96 | }); 97 | ObservableUtil.preserve(s, this); 98 | } 99 | 100 | @Override 101 | public void onDetachFromWindow() { 102 | super.onDetachFromWindow(); 103 | 104 | //remove all bindings 105 | ObservableUtil.dispose(this); 106 | } 107 | 108 | @Override 109 | public void onAttachToWindow() { 110 | super.onAttachToWindow(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/feed/feedStream/view/FeedItemVH.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.feed.feedStream.view; 2 | 3 | import android.view.View; 4 | 5 | import com.mrpeak.trimdata.application.common.BaseViewHolder; 6 | import com.mrpeak.trimdata.application.feed.feedStream.model.*; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/2. 10 | */ 11 | public class FeedItemVH extends BaseViewHolder { 12 | 13 | 14 | public FeedItemVH(View itemView) { 15 | super(itemView); 16 | } 17 | public void bindFeedItem(FeedItem item) { 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/application/setting/SettingListFragment.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.application.setting; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import com.mrpeak.trimdata.R; 12 | import com.mrpeak.trimdata.application.MainTabActivity; 13 | import com.mrpeak.trimdata.application.common.ItemListFragment; 14 | 15 | /** 16 | * A simple {@link Fragment} subclass. 17 | * Activities that contain this fragment must implement the 18 | * {@link SettingListFragment.OnFragmentInteractionListener} interface 19 | * to handle interaction events. 20 | * Use the {@link SettingListFragment#newInstance} factory method to 21 | * create an instance of this fragment. 22 | */ 23 | public class SettingListFragment extends ItemListFragment { 24 | // TODO: Rename parameter arguments, choose names that match 25 | // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER 26 | private static final String ARG_PARAM1 = "param1"; 27 | private static final String ARG_PARAM2 = "param2"; 28 | 29 | // TODO: Rename and change types of parameters 30 | private String mParam1; 31 | private String mParam2; 32 | 33 | public SettingListFragment() { 34 | // Required empty public constructor 35 | } 36 | 37 | /** 38 | * Use this factory method to create a new instance of 39 | * this fragment using the provided parameters. 40 | * 41 | * @param param1 Parameter 1. 42 | * @param param2 Parameter 2. 43 | * @return A new instance of fragment SettingListFragment. 44 | */ 45 | // TODO: Rename and change types and number of parameters 46 | public static SettingListFragment newInstance(String param1, String param2) { 47 | SettingListFragment fragment = new SettingListFragment(); 48 | Bundle args = new Bundle(); 49 | args.putString(ARG_PARAM1, param1); 50 | args.putString(ARG_PARAM2, param2); 51 | fragment.setArguments(args); 52 | return fragment; 53 | } 54 | 55 | @Override 56 | public void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | if (getArguments() != null) { 59 | mParam1 = getArguments().getString(ARG_PARAM1); 60 | mParam2 = getArguments().getString(ARG_PARAM2); 61 | } 62 | } 63 | 64 | @Override 65 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 66 | Bundle savedInstanceState) { 67 | // Inflate the layout for this fragment 68 | return inflater.inflate(R.layout.fragment_setting_list, container, false); 69 | } 70 | 71 | // TODO: Rename method, update argument and hook method into UI event 72 | public void onButtonPressed(Uri uri) { 73 | 74 | } 75 | 76 | @Override 77 | public void onAttach(Context context) { 78 | super.onAttach(context); 79 | // if (context instanceof OnFragmentInteractionListener) { 80 | // mListener = (OnFragmentInteractionListener) context; 81 | // } else { 82 | // throw new RuntimeException(context.toString() 83 | // + " must implement OnFragmentInteractionListener"); 84 | // } 85 | } 86 | 87 | @Override 88 | public void onDetach() { 89 | super.onDetach(); 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/core/TrimApplication.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.core; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | 7 | import com.mrpeak.trimdata.service.ServiceFactory; 8 | 9 | /** 10 | * Created by gaofeng on 16/3/5. 11 | */ 12 | public class TrimApplication extends Application { 13 | private static Context context; 14 | 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | 19 | TrimApplication.context = getApplicationContext(); 20 | 21 | ServiceFactory.getInstance().initEnv(this); 22 | } 23 | 24 | public static Context getAppContext() { 25 | return TrimApplication.context; 26 | } 27 | 28 | @Override 29 | public void onTerminate() { 30 | super.onTerminate(); 31 | } 32 | 33 | @Override 34 | public void onConfigurationChanged(Configuration newConfig) { 35 | super.onConfigurationChanged(newConfig); 36 | } 37 | 38 | @Override 39 | public void onLowMemory() { 40 | super.onLowMemory(); 41 | } 42 | 43 | @Override 44 | public void onTrimMemory(int level) { 45 | super.onTrimMemory(level); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/DalBase.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/3. 5 | */ 6 | public class DalBase { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/DalFactory.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | import android.app.Application; 4 | import android.database.sqlite.SQLiteDatabase; 5 | 6 | import com.activeandroid.*; 7 | import com.google.inject.Guice; 8 | import com.google.inject.Inject; 9 | import com.google.inject.Injector; 10 | 11 | /** 12 | * Created by gaofeng on 16/2/29. 13 | */ 14 | public class DalFactory { 15 | private static DalFactory ourInstance = null; 16 | static { 17 | Injector injector = Guice.createInjector(new DalModule()); 18 | ourInstance = injector.getInstance(DalFactory.class); 19 | } 20 | public static DalFactory getInstance() { 21 | return ourInstance; 22 | } 23 | 24 | 25 | private SQLiteDatabase db; 26 | 27 | @Inject 28 | public IGitHubRepoDal gitHubRepoDal; 29 | 30 | public void initDB(Application application) { 31 | ActiveAndroid.initialize(application); 32 | Configuration dbConfiguration = new Configuration.Builder(application).setDatabaseName("trim.db").create(); 33 | ActiveAndroid.initialize(dbConfiguration); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/DalModule.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.mrpeak.trimdata.dal.imp.GitHubRepoDalImp; 5 | 6 | /** 7 | * Created by gaofeng on 16/3/5. 8 | */ 9 | public class DalModule extends AbstractModule { 10 | @Override 11 | protected void configure() { 12 | bind(IGitHubRepoDal.class).to(GitHubRepoDalImp.class); 13 | } 14 | 15 | public DalModule() { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/EventSource.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | 4 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 5 | 6 | import java.util.ArrayList; 7 | 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.subjects.PublishSubject; 11 | 12 | /** 13 | * Created by gaofeng on 16/3/3. 14 | */ 15 | 16 | 17 | public class EventSource { 18 | private static EventSource ourInstance = new EventSource(); 19 | public static EventSource getInstance() { 20 | return ourInstance; 21 | } 22 | 23 | private EventSource() { 24 | githubEvent = new DataEvent<>(); 25 | } 26 | 27 | public DataEvent githubEvent; 28 | 29 | public class DataEvent { 30 | public PublishSubject insert = PublishSubject.create(); 31 | public PublishSubject delete = PublishSubject.create(); 32 | public PublishSubject update = PublishSubject.create(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/IDalBase.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/5. 5 | */ 6 | public interface IDalBase { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/IGitHubRepoDal.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/5. 10 | */ 11 | public interface IGitHubRepoDal extends IDalBase { 12 | void insertOrUpdateGitHubRepo(GitHubRepository repo); 13 | GitHubRepository getRepoById(Long repoId); 14 | List loadAll(); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/imp/GitHubRepoDalImp.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal.imp; 2 | 3 | import com.activeandroid.query.Select; 4 | import com.mrpeak.trimdata.dal.DalBase; 5 | import com.mrpeak.trimdata.dal.EventSource; 6 | import com.mrpeak.trimdata.dal.IGitHubRepoDal; 7 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by gaofeng on 16/3/5. 14 | */ 15 | public class GitHubRepoDalImp extends DalBase implements IGitHubRepoDal { 16 | 17 | public void insertOrUpdateGitHubRepo(GitHubRepository repo) { 18 | GitHubRepository entity = getRepoById(repo.getRepoId().getValue()); 19 | if (entity != null) { 20 | //do update 21 | entity.setName(repo.getName().getValue()); 22 | entity.setStar(repo.getStar().getValue()); 23 | entity.setFork(repo.getFork().getValue()); 24 | entity.setOpenIssue(repo.getOpenIssue().getValue()); 25 | 26 | entity.saveEntity(); 27 | EventSource.getInstance().githubEvent.update.onNext(repo); 28 | } else { 29 | //do insert 30 | repo.saveEntity(); 31 | EventSource.getInstance().githubEvent.insert.onNext(repo); 32 | } 33 | } 34 | 35 | public GitHubRepository getRepoById(Long repoId) { 36 | return new Select() 37 | .from(GitHubRepository.class) 38 | .where("repoId = ?", repoId) 39 | .executeSingle(); 40 | } 41 | 42 | public List loadAll() { 43 | List all = new Select() 44 | .from(GitHubRepository.class) 45 | .execute(); 46 | 47 | for(GitHubRepository repo : all) { 48 | repo.initModel(); 49 | } 50 | 51 | return all; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/rawModel/GitHubRepository.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal.rawModel; 2 | 3 | import android.util.Log; 4 | 5 | import com.activeandroid.Model; 6 | import com.activeandroid.annotation.Column; 7 | import com.activeandroid.annotation.Table; 8 | import com.google.inject.Inject; 9 | 10 | import rx.functions.Action1; 11 | import rx.subjects.BehaviorSubject; 12 | 13 | /** 14 | * Created by gaofeng on 16/2/29. 15 | */ 16 | 17 | @Table(name = "GitHubRepo") 18 | public class GitHubRepository extends RawModelBase { 19 | 20 | @Column(name = "repoId", index = true) 21 | public Long repoId_c; 22 | 23 | @Column(name = "name") 24 | public String name_c; 25 | 26 | @Column(name = "star") 27 | public Integer star_c; 28 | 29 | @Column(name = "fork") 30 | public Integer fork_c; 31 | 32 | @Column(name = "openIssue") 33 | public Integer openIssue_c; 34 | 35 | @Override 36 | public void saveEntity() { 37 | save(); 38 | } 39 | 40 | private BehaviorSubject repoId; 41 | private BehaviorSubject name; 42 | private BehaviorSubject star; 43 | private BehaviorSubject fork; 44 | private BehaviorSubject openIssue; 45 | 46 | public GitHubRepository(Long id, String name, int star, int fork, int openIssue) { 47 | initProperties(id, name, star, fork, openIssue); 48 | } 49 | 50 | public GitHubRepository() { 51 | } 52 | 53 | public void initModel() { 54 | initProperties(repoId_c, name_c, star_c, fork_c, openIssue_c); 55 | } 56 | 57 | private void initProperties(Long id, String name, int star, int fork, int openIssue) { 58 | 59 | this.repoId = BehaviorSubject.create(id); 60 | this.name = BehaviorSubject.create(name); 61 | this.star = BehaviorSubject.create(star); 62 | this.fork = BehaviorSubject.create(fork); 63 | this.openIssue = BehaviorSubject.create(openIssue); 64 | 65 | this.repoId.subscribe(new Action1() { 66 | @Override 67 | public void call(Long aLong) { 68 | repoId_c = aLong; 69 | } 70 | }); 71 | 72 | this.name.subscribe(new Action1() { 73 | @Override 74 | public void call(String s) { 75 | name_c = s; 76 | } 77 | }); 78 | 79 | this.star.subscribe(new Action1() { 80 | @Override 81 | public void call(Integer integer) { 82 | star_c = integer; 83 | } 84 | }); 85 | 86 | this.fork.subscribe(new Action1() { 87 | @Override 88 | public void call(Integer integer) { 89 | fork_c = integer; 90 | } 91 | }); 92 | 93 | this.openIssue.subscribe(new Action1() { 94 | @Override 95 | public void call(Integer integer) { 96 | openIssue_c = integer; 97 | } 98 | }); 99 | 100 | } 101 | 102 | public BehaviorSubject getRepoId() { 103 | return repoId; 104 | } 105 | 106 | public void setRepoId(Long repoId) { 107 | this.repoId.onNext(repoId); 108 | } 109 | 110 | public BehaviorSubject getName() { 111 | return name; 112 | } 113 | 114 | public void setName(String name) { 115 | this.name.onNext(name); 116 | } 117 | 118 | public BehaviorSubject getStar() { 119 | return star; 120 | } 121 | 122 | public void setStar(Integer star) { 123 | this.star.onNext(star); 124 | } 125 | 126 | public BehaviorSubject getFork() { 127 | return fork; 128 | } 129 | 130 | public void setFork(Integer fork) { 131 | this.fork.onNext(fork); 132 | } 133 | 134 | public BehaviorSubject getOpenIssue() { 135 | return openIssue; 136 | } 137 | 138 | public void setOpenIssue(Integer openIssue) { 139 | this.openIssue.onNext(openIssue); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/dal/rawModel/RawModelBase.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.dal.rawModel; 2 | 3 | import com.activeandroid.Model; 4 | 5 | /** 6 | * Created by gaofeng on 16/3/6. 7 | */ 8 | public abstract class RawModelBase extends Model { 9 | public abstract void saveEntity(); 10 | 11 | public RawModelBase() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/IFeedStreamService.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by gaofeng on 16/3/2. 11 | */ 12 | public interface IFeedStreamService { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/IGitHubService.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/4. 10 | */ 11 | public interface IGitHubService { 12 | public ArrayList loadAllRepositories(); 13 | public void loadAllRepositoriesFromServer(PPHttpRequestDelegate delegate); 14 | 15 | public GitHubRepository getRepositoryById(Long repoId); 16 | public void updateOrInsertRepository(GitHubRepository repo); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/ServiceBase.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/3. 5 | */ 6 | public class ServiceBase { 7 | void clearCache(){} 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/ServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.google.inject.Guice; 7 | import com.google.inject.Inject; 8 | import com.google.inject.Injector; 9 | import com.mrpeak.trimdata.dal.DalFactory; 10 | 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ScheduledExecutorService; 13 | 14 | /** 15 | * Created by gaofeng on 16/2/29. 16 | */ 17 | public class ServiceFactory { 18 | private static ServiceFactory ourInstance = null; 19 | static { 20 | Injector injector = Guice.createInjector(new ServiceModule()); 21 | ourInstance = injector.getInstance(ServiceFactory.class); 22 | } 23 | public static ServiceFactory getInstance() { 24 | return ourInstance; 25 | } 26 | 27 | public static final ScheduledExecutorService serviceExecutor = Executors.newSingleThreadScheduledExecutor(); 28 | 29 | @Inject 30 | public IFeedStreamService feedStreamService; 31 | @Inject 32 | public IGitHubService gitHubService; 33 | 34 | 35 | public void initEnv(Application application) { 36 | DalFactory.getInstance().initDB(application); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/ServiceModule.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.mrpeak.trimdata.service.cache.GitHubServiceCache; 5 | import com.mrpeak.trimdata.service.imp.FeedStreamServiceImp; 6 | import com.mrpeak.trimdata.service.imp.GitHubServiceImp; 7 | import com.mrpeak.trimdata.service.test.FeedStreamServiceTest; 8 | 9 | /** 10 | * Created by gaofeng on 16/3/2. 11 | */ 12 | public class ServiceModule extends AbstractModule { 13 | 14 | @Override 15 | protected void configure() { 16 | bind(IFeedStreamService.class).to(FeedStreamServiceImp.class); 17 | bind(IGitHubService.class).to(GitHubServiceCache.class); 18 | } 19 | 20 | public ServiceModule() { 21 | 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/cache/FeedStreamServiceCache.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.cache; 2 | 3 | import com.mrpeak.trimdata.service.imp.FeedStreamServiceImp; 4 | 5 | /** 6 | * Created by gaofeng on 16/3/2. 7 | */ 8 | public class FeedStreamServiceCache extends FeedStreamServiceImp { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/cache/GitHubServiceCache.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.cache; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.imp.GitHubServiceImp; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | 9 | /** 10 | * Created by gaofeng on 16/3/4. 11 | */ 12 | public class GitHubServiceCache extends GitHubServiceImp { 13 | private HashMap cachedRepoMap = new HashMap<>(); 14 | 15 | 16 | 17 | @Override 18 | public ArrayList loadAllRepositories() { 19 | ArrayList repos = super.loadAllRepositories(); 20 | synchronized(this) { 21 | for (GitHubRepository repo : repos) { 22 | cachedRepoMap.put(repo.getRepoId().getValue(), repo); 23 | } 24 | } 25 | return repos; 26 | } 27 | 28 | @Override 29 | public void updateOrInsertRepository(GitHubRepository repo) { 30 | GitHubRepository existingRepo = getRepositoryById(repo.getRepoId().getValue()); 31 | if (existingRepo != null) { 32 | //application layer will receive property change event 33 | existingRepo.setName(repo.getName().getValue()); 34 | existingRepo.setStar(repo.getStar().getValue()); 35 | existingRepo.setFork(repo.getFork().getValue()); 36 | existingRepo.setOpenIssue(repo.getOpenIssue().getValue()); 37 | } 38 | super.updateOrInsertRepository(repo); 39 | } 40 | 41 | @Override 42 | public GitHubRepository getRepositoryById(Long repoId) { 43 | GitHubRepository existingRepo; 44 | synchronized (this) { 45 | existingRepo = cachedRepoMap.get(repoId); 46 | } 47 | if (existingRepo != null) { 48 | return existingRepo; 49 | } 50 | return super.getRepositoryById(repoId); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/imp/FeedStreamServiceImp.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.imp; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.IFeedStreamService; 5 | import com.mrpeak.trimdata.service.ServiceBase; 6 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 7 | import com.mrpeak.trimdata.service.network.github.GitHubRepositoryListRequest; 8 | 9 | import java.util.ArrayList; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/2. 13 | */ 14 | public class FeedStreamServiceImp extends ServiceBase implements IFeedStreamService { 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/imp/GitHubServiceImp.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.imp; 2 | 3 | import com.mrpeak.trimdata.dal.DalFactory; 4 | import com.mrpeak.trimdata.dal.EventSource; 5 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 6 | import com.mrpeak.trimdata.service.IGitHubService; 7 | import com.mrpeak.trimdata.service.ServiceBase; 8 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 9 | import com.mrpeak.trimdata.service.network.github.GitHubRepositoryListRequest; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by gaofeng on 16/3/4. 16 | */ 17 | public class GitHubServiceImp extends ServiceBase implements IGitHubService { 18 | public GitHubServiceImp() { 19 | 20 | } 21 | 22 | public ArrayList loadAllRepositories() { 23 | ArrayList repos = new ArrayList(); 24 | List entities = DalFactory.getInstance().gitHubRepoDal.loadAll(); 25 | repos.addAll(entities); 26 | return repos; 27 | } 28 | 29 | @Override 30 | public void loadAllRepositoriesFromServer(PPHttpRequestDelegate delegate) { 31 | String username = "music4kid"; 32 | GitHubRepositoryListRequest req = new GitHubRepositoryListRequest(); 33 | if (delegate instanceof GitHubRepositoryListRequest.GitHubRepositoryListRequestDelegate) { 34 | req.reqDelegate = (GitHubRepositoryListRequest.GitHubRepositoryListRequestDelegate) delegate; 35 | } 36 | req.executeRequest(req.getGitHubServiceProvider().userReposList(username), delegate); 37 | } 38 | 39 | 40 | @Override 41 | public void updateOrInsertRepository(GitHubRepository repo) { 42 | DalFactory.getInstance().gitHubRepoDal.insertOrUpdateGitHubRepo(repo); 43 | } 44 | 45 | @Override 46 | public GitHubRepository getRepositoryById(Long repoId) { 47 | return DalFactory.getInstance().gitHubRepoDal.getRepoById(repoId); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/PPHttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.Callback; 8 | import retrofit2.Response; 9 | import retrofit2.Retrofit; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/3. 13 | */ 14 | 15 | public class PPHttpRequest { 16 | public static final int HTTP_REQUEST_CONNECTION_TIMEOUT = 15 * 1000; 17 | public static final int HTTP_REQUEST_READ_TIMEOUT = 15 * 1000; 18 | public static final int HTTP_REQUEST_WRITE_TIMEOUT = 15 * 1000; 19 | 20 | protected PPHttpRequestDelegate delegate = null; 21 | 22 | public void executeRequest(Call call, PPHttpRequestDelegate del) { 23 | this.delegate = del; 24 | call.enqueue(new Callback() { 25 | @Override 26 | public void onResponse(Call call, Response response) { 27 | PPHttpRequest.this.onRequestSuccess(response); 28 | } 29 | 30 | @Override 31 | public void onFailure(Call call, Throwable t) { 32 | PPHttpRequest.this.onRequestFail(t); 33 | } 34 | }); 35 | } 36 | 37 | protected void onRequestFail(Throwable t) { 38 | if (delegate != null) { 39 | delegate.onRequestFail(t.getMessage()); 40 | } 41 | } 42 | 43 | protected void onRequestSuccess(retrofit2.Response response) { 44 | if (response.isSuccess()) { 45 | if (delegate != null) { 46 | delegate.onRequestSuccess(this, 0); 47 | } 48 | } else { 49 | if (delegate != null) { 50 | delegate.onRequestFail(response.message()); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/PPHttpRequestDelegate.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/3. 5 | */ 6 | public interface PPHttpRequestDelegate { 7 | void onRequestSuccess(PPHttpRequest req, int code); 8 | void onRequestFail(String msg); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/github/GitHubRepositoryListRequest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network.github; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.ServiceFactory; 5 | import com.mrpeak.trimdata.service.network.retrofitModel.RfRepository; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import retrofit2.Response; 11 | 12 | /** 13 | * Created by gaofeng on 16/3/4. 14 | */ 15 | public class GitHubRepositoryListRequest extends GitHubRequest { 16 | public GitHubRepositoryListRequestDelegate reqDelegate = null; 17 | @Override 18 | protected void onRequestSuccess(Response response) { 19 | if (response.isSuccess()) { 20 | ArrayList repos = new ArrayList<>(); 21 | List items = (List) response.body(); 22 | for (RfRepository item : items) { 23 | GitHubRepository repo = new GitHubRepository(Long.parseLong(""+item.getId()), 24 | item.getName(), 25 | item.getStargazers_count(), 26 | item.getForks_count(), 27 | item.getOpen_issues_count()); 28 | repos.add(repo); 29 | } 30 | 31 | //notify application layer 32 | if (reqDelegate != null) { 33 | reqDelegate.getGitHubRepoFinished(repos); 34 | } 35 | 36 | //save data 37 | for (GitHubRepository repo : repos) { 38 | ServiceFactory.getInstance().gitHubService.updateOrInsertRepository(repo); 39 | } 40 | 41 | } else { 42 | if (delegate != null) { 43 | delegate.onRequestFail(""); 44 | } 45 | } 46 | } 47 | 48 | public interface GitHubRepositoryListRequestDelegate { 49 | public void getGitHubRepoFinished(ArrayList repos); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/github/GitHubRequest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network.github; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.mrpeak.trimdata.service.network.PPHttpRequest; 6 | 7 | import java.security.KeyManagementException; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.SecureRandom; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import javax.net.ssl.HostnameVerifier; 13 | import javax.net.ssl.HttpsURLConnection; 14 | import javax.net.ssl.SSLContext; 15 | import javax.net.ssl.SSLSession; 16 | import javax.net.ssl.TrustManager; 17 | 18 | import okhttp3.OkHttpClient; 19 | import retrofit2.Retrofit; 20 | import retrofit2.converter.gson.GsonConverterFactory; 21 | 22 | /** 23 | * Created by gaofeng on 16/3/4. 24 | */ 25 | public class GitHubRequest extends PPHttpRequest { 26 | public static String GitHubAPI = "https://api.github.com/"; 27 | private GitHubRequestProvider githubServiceProvider; 28 | 29 | public GitHubRequest() { 30 | githubServiceProvider = GitHubRequest.getRetrofit().create(GitHubRequestProvider.class); 31 | } 32 | 33 | public GitHubRequestProvider getGitHubServiceProvider() { 34 | return githubServiceProvider; 35 | } 36 | 37 | public static String getBaseUrl() { 38 | return GitHubAPI; 39 | } 40 | 41 | public static Retrofit getRetrofit() { 42 | Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").create(); 43 | 44 | Retrofit retrofit = new Retrofit.Builder() 45 | .baseUrl(getBaseUrl()) 46 | .addConverterFactory(GsonConverterFactory.create(gson)) 47 | .client(getOkClient()).build(); 48 | 49 | return retrofit; 50 | } 51 | 52 | 53 | 54 | public static OkHttpClient getOkClient() { 55 | OkHttpClient client = new OkHttpClient.Builder() 56 | .connectTimeout(HTTP_REQUEST_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) 57 | .readTimeout(HTTP_REQUEST_READ_TIMEOUT, TimeUnit.MILLISECONDS) 58 | .writeTimeout(HTTP_REQUEST_WRITE_TIMEOUT, TimeUnit.MILLISECONDS) 59 | .hostnameVerifier(new HostnameVerifier() { 60 | @Override 61 | public boolean verify(String hostname, SSLSession session) { 62 | return true; 63 | } 64 | }) 65 | .build(); 66 | 67 | return client; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/github/GitHubRequestProvider.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network.github; 2 | 3 | import com.mrpeak.trimdata.service.network.retrofitModel.RfRepository; 4 | 5 | import java.util.List; 6 | 7 | import retrofit2.Call; 8 | import retrofit2.http.GET; 9 | import retrofit2.http.Path; 10 | 11 | /** 12 | * Created by gaofeng on 16/3/4. 13 | */ 14 | public interface GitHubRequestProvider { 15 | @GET("users/{username}/repos?type=owner") 16 | Call> userReposList(@Path("username") String username); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/network/retrofitModel/RfRepository.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.network.retrofitModel; 2 | 3 | /** 4 | * Created by gaofeng on 16/3/4. 5 | */ 6 | public class RfRepository { 7 | private int id; 8 | private String name; 9 | 10 | private int stargazers_count; 11 | private int forks_count; 12 | private int open_issues_count; 13 | 14 | public RfRepository() { 15 | } 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | public int getStargazers_count() { 34 | return stargazers_count; 35 | } 36 | 37 | public void setStargazers_count(int stargazers_count) { 38 | this.stargazers_count = stargazers_count; 39 | } 40 | 41 | public int getForks_count() { 42 | return forks_count; 43 | } 44 | 45 | public void setForks_count(int forks_count) { 46 | this.forks_count = forks_count; 47 | } 48 | 49 | public int getOpen_issues_count() { 50 | return open_issues_count; 51 | } 52 | 53 | public void setOpen_issues_count(int open_issues_count) { 54 | this.open_issues_count = open_issues_count; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/test/FeedStreamServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.test; 2 | 3 | import com.mrpeak.trimdata.dal.EventSource; 4 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 5 | import com.mrpeak.trimdata.service.ServiceFactory; 6 | import com.mrpeak.trimdata.service.cache.FeedStreamServiceCache; 7 | import com.mrpeak.trimdata.service.network.PPHttpRequestDelegate; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * Created by gaofeng on 16/3/2. 16 | */ 17 | public class FeedStreamServiceTest extends FeedStreamServiceCache { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/mrpeak/trimdata/service/test/GitHubServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.mrpeak.trimdata.service.test; 2 | 3 | import com.mrpeak.trimdata.dal.rawModel.GitHubRepository; 4 | import com.mrpeak.trimdata.service.cache.GitHubServiceCache; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * Created by gaofeng on 16/3/11. 10 | */ 11 | 12 | public class GitHubServiceTest extends GitHubServiceCache { 13 | @Override 14 | public ArrayList loadAllRepositories() { 15 | ArrayList repos = super.loadAllRepositories(); 16 | //run some tests here... 17 | return repos; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_feed_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_feed_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 24 | 25 | 30 | 31 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_itemlist_github_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_itemlist_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_setting_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/app_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/res/mipmap-hdpi/app_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/app_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/res/mipmap-mdpi/app_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/app_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/res/mipmap-xhdpi/app_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/app_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/res/mipmap-xxhdpi/app_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/app_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/music4kid/Data-Driven-Architecture-Android/b1eec07b1c8f4ecef761ada218b731f96dafc086/app/src/main/res/mipmap-xxxhdpi/app_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 64dp 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 5 | 16dp 6 | 16dp 7 | 16dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Trim 3 | Settings 4 | 5 | 6 | Hello blank fragment 7 | FeedDetailActivity 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |