├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ryanair │ │ │ └── cheapflights │ │ │ ├── CheapFlightsApplication.java │ │ │ ├── di │ │ │ ├── FlightInfoResultsModule.java │ │ │ └── FlightInfoSearchModule.java │ │ │ └── ui │ │ │ ├── BaseActivity.java │ │ │ ├── Extras.java │ │ │ └── flightinformation │ │ │ ├── FlightInfoResultsActivity.java │ │ │ ├── FlightInfoResultsAdapter.java │ │ │ └── FlightInfoSearchActivity.java │ └── res │ │ ├── drawable-hdpi │ │ ├── drawer_shadow.9.png │ │ └── ic_drawer.png │ │ ├── drawable-mdpi │ │ ├── drawer_shadow.9.png │ │ └── ic_drawer.png │ │ ├── drawable-xhdpi │ │ ├── drawer_shadow.9.png │ │ └── ic_drawer.png │ │ ├── drawable-xxhdpi │ │ ├── drawer_shadow.9.png │ │ └── ic_drawer.png │ │ ├── layout │ │ ├── activity_flight_info_results.xml │ │ ├── activity_flight_info_search.xml │ │ ├── activity_main.xml │ │ └── item_flight_info.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ryanair │ └── cheapflights │ ├── domain │ └── command │ │ └── GetFlightInfoTest.java │ └── util │ └── ObservableUtil.java ├── build.gradle ├── config ├── quality.gradle └── quality │ └── checkstyle │ └── checkstyle.xml ├── data ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ryanair │ └── cheapflights │ └── data │ └── api │ └── dotRez │ ├── Api.java │ ├── model │ └── flightinformation │ │ ├── FlightInfoRequest.java │ │ └── FlightInfoResponse.java │ └── service │ └── FlightInfoService.java ├── domain ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ryanair │ └── cheapflights │ └── domain │ └── command │ └── GetFlightInfo.java ├── entity ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ryanair │ └── cheapflights │ └── entity │ ├── Airport.java │ └── FlightInfo.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presentation ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ryanair │ └── cheapflights │ └── presentation │ ├── presenter │ ├── FlightInfoResultsPresenter.java │ ├── FlightInfoSearchPresenter.java │ └── Presenter.java │ └── view │ ├── FlightInfoResultsView.java │ └── FlightInfoSearchView.java ├── repository ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ryanair │ └── cheapflights │ └── repository │ └── FlightInfoRepository.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | .DS_Store 4 | /build 5 | /captures 6 | *.iml 7 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #TL;DR 2 | The architecture employs MVP and CQRS patterns. RxJava is used to bind layers, provide asynchrony and make async calls testable. 3 | 4 | #Problem 5 | The usual DDDD (Deadline Driven Development or Die; also known as 4D) approach has lead to non-structural and not-testable codebase with growing complexity. 6 | All code belongs to one module. Communication with API and business is performed within UI code. Code is not testable due to coupling with external services and the platform. 7 | 8 | #Requirements 9 | Application code shold be divided into separate layers which don't depend on each other. 10 | Layers should be easily mocked and/or changed with another implementation. API business logic should not depend on what API is used to retrieve data. 11 | Business logic has to be easily unit tested. 12 | Responsibilities between components should be clearly defined. 13 | 14 | #Solution 15 | MVP, CQRS, Rx 16 | ![Onion structure](http://i284.photobucket.com/albums/ll17/Vlado_Atanasov/OnionNew_zpsazui7lqd.jpg) 17 | 18 | #Project Structure 19 | 20 | ##Entity 21 | POJO business entities shared accros the app. 22 | 23 | ##Data 24 | Communication with external services (REST API, Couchbase) goes to the data package. All models wich belong to specifics of API data structure stay here. 25 | 26 | ###Example: 27 | FlightInfoService implements communication with REST API. 28 | It uses FlightRequest and FlightResponse classes to map API data structure. 29 | 30 | ##Repository 31 | Repositories handles data retrieval from external services and mapping to common entities. 32 | 33 | ##Domain 34 | Business logic is implemented in form of separate command/query classes for each action. 35 | Instead of having one StationController/Util/Service with methods like getAllStations, getStationByName, getNearbyStations, there will be separate class for each method. 36 | 37 | ###Example: 38 | ```java 39 | package com.ryanair.cheapflights.domain.station; 40 | public class GetNearbyStation { 41 | public GetNearbyStation(StationRepository repository) { 42 | // ... 43 | } 44 | public Observable execute(Location currentUserLocation) { 45 | // find stations in 250km radius 46 | } 47 | } 48 | ``` 49 | 50 | ##Presentation 51 | Holds view interfaces and presenters (see MVP pattern). 52 | The view is a passive interface that displays data (the model) and routes user commands (events) to the presenter to act upon that data. 53 | The presenter acts upon the model and the view. It retrieves data from the model, and formats it for display in the view. 54 | 55 | ##App 56 | All platform related code. Implementation of views. Instantiating API services and providing endpoint. Legacy code is kept here as well. 57 | 58 | ##Dependency Injection 59 | Dagger 2 60 | 61 | ##Reactive 62 | Code across the app is bound by RxJava's Observable class. Using reactive approach allows to write asynchrony-enabled code on each level of abstraction and leave background threading and asynchrony support to the final consumer of the data. 63 | RxJava also allows to write unit tests against asynchronous code without changing implementation, workarounds or any kind of threading synchronisation code. Basically there out-of-the-box ability to convert async result into synchronous feed. 64 | Repositories map and return data reactive way. 65 | ```java 66 | public class FlightInfoRepositoryDotRez { 67 | public FlightInfoRepositoryDotRez(FlightInfoService service) { 68 | flightInfoService = service; 69 | } 70 | 71 | public Observalbe getByRoute(String from, String to) { 72 | FlightInfoRequest request = new FlightInfoRequest(from, to); 73 | 74 | return service.getFlightInfo(request) 75 | .map(new Func1>() { 76 | @Override 77 | public List call(FlightInfoResponse response) { 78 | List results = new ArrayList<>(); 79 | 80 | // map to FlightInfo 81 | 82 | return results; 83 | } 84 | }); 85 | } 86 | } 87 | ``` 88 | 89 | Business logic consumes observable collections, apply business rules and return results in form of reactive observable result. 90 | The result of the work above goes to presenters where reactive data being applied to views in asynchronous way. 91 | 92 | ##Testing 93 | JUnit 94 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../config/quality.gradle' 3 | 4 | android { 5 | compileSdkVersion 22 6 | buildToolsVersion "22.0.1" 7 | 8 | defaultConfig { 9 | applicationId "com.ryanair.cheapflights" 10 | minSdkVersion 16 11 | targetSdkVersion 22 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | packagingOptions { 23 | exclude 'META-INF/services/javax.annotation.processing.Processor' 24 | exclude 'LICENSE.txt' 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_7 28 | targetCompatibility JavaVersion.VERSION_1_7 29 | } 30 | } 31 | 32 | dependencies { 33 | compile fileTree(dir: 'libs', include: ['*.jar']) 34 | compile project(':presentation') 35 | 36 | compile 'com.android.support:appcompat-v7:22.1.1' 37 | compile 'com.android.support:recyclerview-v7:21.0.0' 38 | compile 'com.android.support:cardview-v7:21.0.0' 39 | compile 'com.jakewharton:butterknife:6.1.0' 40 | compile 'com.squareup.dagger:dagger:1.2.2' 41 | provided 'com.squareup.dagger:dagger-compiler:1.2.2' 42 | compile 'javax.inject:javax.inject:1' 43 | compile 'io.reactivex:rxandroid:0.24.0' 44 | 45 | testCompile 'junit:junit:4.12' 46 | testCompile 'org.mockito:mockito-core:1.9.5' 47 | testCompile 'org.assertj:assertj-core:1.7.1' // latest Android suitable version 48 | 49 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1' 50 | androidTestCompile 'com.android.support.test:runner:0.2' 51 | androidTestCompile 'com.android.support:support-annotations:22.1.1' 52 | androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.0' 53 | } 54 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/bashkirovs/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/CheapFlightsApplication.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | public class CheapFlightsApplication extends Application { 7 | @Override 8 | public void onCreate() { 9 | super.onCreate(); 10 | } 11 | 12 | public static CheapFlightsApplication get(Context context) { 13 | return (CheapFlightsApplication) context.getApplicationContext(); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/di/FlightInfoResultsModule.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.di; 2 | 3 | import com.ryanair.cheapflights.data.api.dotRez.Api; 4 | import com.ryanair.cheapflights.data.api.dotRez.service.FlightInfoService; 5 | import com.ryanair.cheapflights.domain.command.GetFlightInfo; 6 | import com.ryanair.cheapflights.presentation.presenter.FlightInfoResultsPresenter; 7 | import com.ryanair.cheapflights.repository.FlightInfoRepository; 8 | import com.ryanair.cheapflights.ui.flightinformation.FlightInfoResultsActivity; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | 13 | @Module( 14 | injects = FlightInfoResultsActivity.class 15 | ) 16 | public class FlightInfoResultsModule { 17 | private final FlightInfoResultsActivity flightInfoResultsActivity; 18 | 19 | public FlightInfoResultsModule(FlightInfoResultsActivity activity) { 20 | this.flightInfoResultsActivity = activity; 21 | } 22 | 23 | @Provides 24 | public FlightInfoResultsPresenter provideFlightInfoSearchPresenter() { 25 | FlightInfoService service = Api.create(FlightInfoService.class); 26 | FlightInfoRepository repository = new FlightInfoRepository(service); 27 | GetFlightInfo getFlightInfo = new GetFlightInfo(repository); 28 | return new FlightInfoResultsPresenter(flightInfoResultsActivity, getFlightInfo); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/di/FlightInfoSearchModule.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.di; 2 | 3 | import com.ryanair.cheapflights.presentation.presenter.FlightInfoSearchPresenter; 4 | import com.ryanair.cheapflights.ui.flightinformation.FlightInfoSearchActivity; 5 | 6 | import dagger.Module; 7 | import dagger.Provides; 8 | 9 | @Module( 10 | injects = FlightInfoSearchActivity.class 11 | ) 12 | public class FlightInfoSearchModule { 13 | private final FlightInfoSearchActivity flightInfoSearchActivity; 14 | 15 | public FlightInfoSearchModule(FlightInfoSearchActivity activity) { 16 | this.flightInfoSearchActivity = activity; 17 | } 18 | 19 | @Provides 20 | public FlightInfoSearchPresenter provideFlightInfoSearchPresenter() { 21 | return new FlightInfoSearchPresenter(flightInfoSearchActivity); 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/ui/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import butterknife.ButterKnife; 7 | 8 | public abstract class BaseActivity extends AppCompatActivity { 9 | protected abstract int getContentView(); 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(getContentView()); 15 | ButterKnife.inject(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/ui/Extras.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.ui; 2 | 3 | public final class Extras { 4 | public static final String FLIGHT_NUMBER = "FLIGHT_NUMBER"; 5 | 6 | private Extras() { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/ui/flightinformation/FlightInfoResultsActivity.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.ui.flightinformation; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.widget.DefaultItemAnimator; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.widget.Toast; 8 | 9 | import com.ryanair.cheapflights.R; 10 | import com.ryanair.cheapflights.di.FlightInfoResultsModule; 11 | import com.ryanair.cheapflights.entity.FlightInfo; 12 | import com.ryanair.cheapflights.presentation.presenter.FlightInfoResultsPresenter; 13 | import com.ryanair.cheapflights.presentation.view.FlightInfoResultsView; 14 | import com.ryanair.cheapflights.ui.BaseActivity; 15 | import com.ryanair.cheapflights.ui.Extras; 16 | 17 | import java.util.List; 18 | 19 | import javax.inject.Inject; 20 | 21 | import butterknife.InjectView; 22 | import dagger.ObjectGraph; 23 | 24 | public class FlightInfoResultsActivity extends BaseActivity implements FlightInfoResultsView { 25 | @Inject 26 | FlightInfoResultsPresenter presenter; 27 | 28 | @InjectView(R.id.flight_info_results_list) 29 | RecyclerView resultsList; 30 | 31 | private FlightInfoResultsAdapter adapter; 32 | 33 | @Override 34 | protected int getContentView() { 35 | return R.layout.activity_flight_info_results; 36 | } 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | 42 | ObjectGraph objectGraph = ObjectGraph.create(new FlightInfoResultsModule(this)); 43 | objectGraph.inject(this); 44 | 45 | presenter.startSearch(); 46 | 47 | resultsList.setLayoutManager(new LinearLayoutManager(this)); 48 | resultsList.setItemAnimator(new DefaultItemAnimator()); 49 | 50 | adapter = new FlightInfoResultsAdapter(new FlightInfoResultsAdapter.OnSelectedListener() { 51 | @Override 52 | public void onFlightSelected(FlightInfo flightInfo) { 53 | presenter.selectFlight(flightInfo); 54 | 55 | } 56 | }); 57 | resultsList.setAdapter(adapter); 58 | } 59 | 60 | @Override 61 | public String getFlightNumber() { 62 | return getIntent().getStringExtra(Extras.FLIGHT_NUMBER); 63 | } 64 | 65 | @Override 66 | public void displayFlightInfo(List flightInfo) { 67 | adapter.setFlightInfoResults(flightInfo); 68 | } 69 | 70 | @Override 71 | public void displayError(Throwable error) { 72 | Toast.makeText(this, "Oops! Something unholy just happened", Toast.LENGTH_SHORT).show(); 73 | } 74 | 75 | @Override 76 | public void displaySelectedFlight(FlightInfo flightInfo) { 77 | Toast.makeText(getBaseContext(), flightInfo.getFlightNumber(), Toast.LENGTH_SHORT).show(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/ui/flightinformation/FlightInfoResultsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.ui.flightinformation; 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 | import android.widget.TextView; 9 | 10 | import com.ryanair.cheapflights.R; 11 | import com.ryanair.cheapflights.entity.FlightInfo; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import butterknife.ButterKnife; 17 | import butterknife.InjectView; 18 | 19 | public class FlightInfoResultsAdapter extends RecyclerView.Adapter { 20 | private final OnSelectedListener onSelectedListener; 21 | private final List flightInfoResults; 22 | 23 | public FlightInfoResultsAdapter(OnSelectedListener listener) { 24 | this.flightInfoResults = new ArrayList<>(); 25 | this.onSelectedListener = listener; 26 | } 27 | 28 | @Override 29 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 30 | Context context = viewGroup.getContext(); 31 | View view = LayoutInflater.from(context).inflate(R.layout.item_flight_info, viewGroup, false); 32 | return new ViewHolder(view); 33 | } 34 | 35 | @Override 36 | public void onBindViewHolder(ViewHolder viewHolder, int i) { 37 | final FlightInfo flightInfo = flightInfoResults.get(i); 38 | 39 | viewHolder.root.setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | onSelectedListener.onFlightSelected(flightInfo); 43 | } 44 | }); 45 | viewHolder.number.setText(flightInfo.getFlightNumber()); 46 | } 47 | 48 | @Override 49 | public int getItemCount() { 50 | return flightInfoResults.size(); 51 | } 52 | 53 | public void setFlightInfoResults(List results) { 54 | flightInfoResults.addAll(results); 55 | notifyDataSetChanged(); 56 | } 57 | 58 | public interface OnSelectedListener { 59 | void onFlightSelected(FlightInfo flightInfo); 60 | } 61 | 62 | public static class ViewHolder extends RecyclerView.ViewHolder { 63 | @InjectView(R.id.item_flight_info_root) 64 | View root; 65 | 66 | @InjectView(R.id.item_flight_info_number) 67 | TextView number; 68 | 69 | public ViewHolder(View itemView) { 70 | super(itemView); 71 | ButterKnife.inject(this, itemView); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/ryanair/cheapflights/ui/flightinformation/FlightInfoSearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.ryanair.cheapflights.ui.flightinformation; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.widget.EditText; 6 | 7 | import com.ryanair.cheapflights.R; 8 | import com.ryanair.cheapflights.di.FlightInfoSearchModule; 9 | import com.ryanair.cheapflights.presentation.presenter.FlightInfoSearchPresenter; 10 | import com.ryanair.cheapflights.presentation.view.FlightInfoSearchView; 11 | import com.ryanair.cheapflights.ui.BaseActivity; 12 | import com.ryanair.cheapflights.ui.Extras; 13 | 14 | import javax.inject.Inject; 15 | 16 | import butterknife.InjectView; 17 | import butterknife.OnClick; 18 | import dagger.ObjectGraph; 19 | 20 | public class FlightInfoSearchActivity extends BaseActivity implements FlightInfoSearchView { 21 | @Inject 22 | FlightInfoSearchPresenter presenter; 23 | 24 | @InjectView(R.id.flight_info_search_edit_flight) 25 | EditText flightEdit; 26 | 27 | @Override 28 | protected int getContentView() { 29 | return R.layout.activity_flight_info_search; 30 | } 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | ObjectGraph objectGraph = ObjectGraph.create(new FlightInfoSearchModule(this)); 37 | objectGraph.inject(this); 38 | } 39 | 40 | @OnClick(R.id.flight_info_search_button_search) 41 | void onSearchClick() { 42 | presenter.search(); 43 | } 44 | 45 | @Override 46 | public void goToSearchResults(String flightNumber) { 47 | Intent searchResultsIntent = new Intent(this, FlightInfoResultsActivity.class); 48 | searchResultsIntent.putExtra(Extras.FLIGHT_NUMBER, flightNumber); 49 | startActivity(searchResultsIntent); 50 | } 51 | 52 | @Override 53 | public String getFlightNumber() { 54 | return flightEdit.getText().toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-hdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-mdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-xhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryanair/android-layered-architecture-example/3989000c46384466c158ff2e65f22b8a5f9852f6/app/src/main/res/drawable-xxhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_flight_info_results.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_flight_info_search.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 18 | 19 |