├── .gitignore
├── LICENSE
├── README.md
├── acc_android_studio.png
├── globals.xml.ftl
├── recipe.xml.ftl
├── root
└── src
│ └── app_package
│ ├── Activity.java.ftl
│ ├── Configurator.java.ftl
│ ├── Interactor.java.ftl
│ ├── Model.java.ftl
│ ├── Presenter.java.ftl
│ ├── PresenterUnitTest.java.ftl
│ ├── Router.java.ftl
│ └── Worker.java.ftl
└── template.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | projectFilesBackup/
3 | gradle/
4 | .gradle/
5 | .idea/**
6 | .notidea/
7 | *.DS_Store
8 | *.iml
9 |
10 | # built application files
11 | *.apk
12 | *.ap_
13 |
14 | # files for the dex VM
15 | *.dex
16 |
17 | # Java class files
18 | *.class
19 |
20 | # built native files (uncomment if you build your own)
21 | # *.o
22 | # *.so
23 |
24 | # generated files
25 | bin/
26 | gen/
27 |
28 | # Ignore gradle files
29 | .gradle/
30 | build/
31 |
32 | # Local configuration file (sdk path, etc)
33 | local.properties
34 |
35 | # Proguard folder generated by Eclipse
36 | proguard/
37 |
38 | # Eclipse Metadata
39 | .metadata/
40 |
41 | # Mac OS X clutter
42 | *.DS_Store
43 |
44 | # Windows clutter
45 | Thumbs.db
46 |
47 | # Intellij IDEA (see https://intellij-support.jetbrains.com/entries/23393067)
48 | .idea/workspace.xml
49 | .idea/tasks.xml
50 | .idea/datasources.xml
51 | .idea/dataSources.ids
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Mohanraj K.M.
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 | # Android Clean Code Template Tool - Auto generate needed files from Android Studio
2 | #### Inspired from [Clean Architecture][1] from Uncle Bob, [Google Android samples][3] and [Clean Swift][2]
3 | ##### This design is chosen with a singular focus - testablity.
4 | ##### Example of this template in action [here][5]
5 |
6 | ## Quick Start
7 | * Clone this project and copy the contents to the following location in your Android Studio
8 |
9 | * Windows : Navigate to the location of the templates folder :
10 | ```
11 | cd {ANDROID_STUDIO_LOCATION}/plugins/android/lib/templates/other/
12 | ```
13 |
14 | * MacOS: Navigate to the location of the templates folder :
15 | ```
16 | cd /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/other/
17 | ```
18 |
19 | * Restart Android Studio - It should work with out any issues
20 |
21 |
22 | ### Contribute
23 | Welcome to contribute, feel free to change and open a PR.
24 |
25 | ### License
26 | [MIT License][6]
27 |
28 | #### TODO
29 | 1. Create scaffolding for Fragments
30 | 2. Create scaffolding for TestClasses
31 |
32 |
33 |
34 | #### Credits
35 | This project ideas aren't new in any way. Credit has to be given to the following projects, listed in autobiographical order.
36 |
37 | [Clean Architecture][1]
38 |
39 | [clean-swift][2]
40 |
41 | [Make your own File Templates in Android Studio][4]
42 |
43 |
44 |
45 | [1]: https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
46 | [2]: http://clean-swift.com
47 | [3]: https://github.com/googlesamples/android-testing
48 | [4]: https://riggaroo.co.za/custom-file-templates-android-studio/
49 | [5]: https://github.com/kmmraj/android-clean-code
50 | [6]: ./LICENSE
51 |
--------------------------------------------------------------------------------
/acc_android_studio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmraj/androidcleancode-generator/fd2c05b541c3a42ccce8d31949fc678ef389cf41/acc_android_studio.png
--------------------------------------------------------------------------------
/globals.xml.ftl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/recipe.xml.ftl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
8 |
10 |
12 |
13 |
15 |
17 |
19 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/root/src/app_package/Activity.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 |
7 |
8 | interface ${classname}ActivityInput {
9 | public void display${classname}Data(${classname}ViewModel viewModel);
10 | }
11 |
12 |
13 | public class ${classname}Activity extends AppCompatActivity
14 | implements ${classname}ActivityInput {
15 |
16 | ${classname}InteractorInput output;
17 | ${classname}Router router;
18 |
19 | public static String TAG = ${classname}Activity.class.getSimpleName();
20 |
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | //do the setup
26 |
27 | ${classname}Configurator.INSTANCE.configure(this);
28 | ${classname}Request a${classname}Request = new ${classname}Request();
29 | //populate the request
30 |
31 |
32 | output.fetch${classname}Data(a${classname}Request);
33 | // Do other work
34 | }
35 |
36 |
37 |
38 | @Override
39 | public void display${classname}Data(${classname}ViewModel viewModel) {
40 | Log.e(TAG, "display${classname}Data() called with: viewModel = [" + viewModel + "]");
41 | // Deal with the data
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/root/src/app_package/Configurator.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | import java.lang.ref.WeakReference;
4 |
5 |
6 | public enum ${classname}Configurator {
7 | INSTANCE;
8 | public void configure(${classname}Activity activity){
9 |
10 | ${classname}Router router = new ${classname}Router();
11 | router.activity = new WeakReference<>(activity);
12 |
13 | ${classname}Presenter presenter = new ${classname}Presenter();
14 | presenter.output = new WeakReference<${classname}ActivityInput>(activity);
15 |
16 | ${classname}Interactor interactor = new ${classname}Interactor();
17 | interactor.output = presenter;
18 |
19 | if (activity.output == null){
20 | activity.output = interactor;
21 | }
22 | if (activity.router == null){
23 | activity.router = router;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/root/src/app_package/Interactor.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | import android.util.Log;
4 |
5 | interface ${classname}InteractorInput{
6 | public void fetch${classname}Data(${classname}Request request);
7 | }
8 |
9 |
10 | public class ${classname}Interactor implements ${classname}InteractorInput {
11 |
12 | public ${classname}PresenterInput output;
13 | public ${classname}WorkerInput a${classname}WorkerInput;
14 |
15 |
16 |
17 | public ${classname}WorkerInput get${classname}WorkerInput() {
18 | if (a${classname}WorkerInput == null) return new ${classname}Worker();
19 | return a${classname}WorkerInput;
20 | }
21 |
22 | public void set${classname}WorkerInput(${classname}WorkerInput a${classname}WorkerInput) {
23 | this.a${classname}WorkerInput = a${classname}WorkerInput;
24 | }
25 |
26 | public static String TAG = ${classname}Interactor.class.getSimpleName();
27 |
28 | @Override
29 | public void fetch${classname}Data(${classname}Request request) {
30 | a${classname}WorkerInput = get${classname}WorkerInput();
31 | ${classname}Response ${classname}Response = new ${classname}Response();
32 | // Call the workers
33 |
34 | output.present${classname}Data(${classname}Response);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/root/src/app_package/Model.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | public class ${classname}Model {
4 | }
5 | class ${classname}ViewModel{
6 | //filter to have only the needed data
7 |
8 | }
9 | class ${classname}Request{
10 |
11 | }
12 |
13 | class ${classname}Response {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/root/src/app_package/Presenter.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | import android.support.annotation.NonNull;
4 | import android.util.Log;
5 | import java.lang.ref.WeakReference;
6 | import java.util.ArrayList;
7 | import java.util.Calendar;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | interface ${classname}PresenterInput {
11 | public void present${classname}Data(${classname}Response response);
12 | }
13 |
14 |
15 | public class ${classname}Presenter implements ${classname}PresenterInput {
16 |
17 | public static String TAG = ${classname}Presenter.class.getSimpleName();
18 |
19 | //weak var output: HomePresenterOutput!
20 | public WeakReference<${classname}ActivityInput> output;
21 |
22 |
23 | @Override
24 | public void present${classname}Data(${classname}Response response) {
25 | // Log.e(TAG, "present${classname}Data() called with: response = [" + response + "]");
26 | //Do your decoration or filtering here
27 |
28 | }
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/root/src/app_package/PresenterUnitTest.java.ftl:
--------------------------------------------------------------------------------
1 | package package ${packageName};
2 |
3 | import com.amadeus.flightstatuslistview.BuildConfig;
4 | import com.amadeus.flightstatuslistview.FlightModel;
5 |
6 | import org.junit.After;
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.robolectric.RobolectricGradleTestRunner;
12 | import org.robolectric.annotation.Config;
13 |
14 | import java.lang.ref.WeakReference;
15 | import java.util.ArrayList;
16 | import java.util.Calendar;
17 |
18 | /**
19 | * Created by mkaratadipalayam on 11/10/16.
20 | */
21 | @RunWith(RobolectricGradleTestRunner.class)
22 | @Config(constants = BuildConfig.class, manifest = "app/src/main/AndroidManifest.xml", sdk = 21)
23 | public class HomePresenterUnitTest {
24 | @Before
25 | public void setUp(){
26 |
27 | }
28 | @After
29 | public void tearDown(){
30 |
31 | }
32 |
33 | @Test
34 | public void presentHomeMetaData_with_vaildInput_shouldCall_displayHomeMetaData(){
35 | //Given
36 | HomePresenter homePresenter = new HomePresenter();
37 | HomeResponse homeResponse = new HomeResponse();
38 | homeResponse.listOfFlights = new FlightWorker().getFutureFlights();
39 |
40 | HomePresenterOutputSpy homePresenterOutputSpy = new HomePresenterOutputSpy();
41 | homePresenter.output = new WeakReference(homePresenterOutputSpy);
42 |
43 | //When
44 | homePresenter.presentHomeMetaData(homeResponse);
45 |
46 | //Then
47 | Assert.assertTrue("When the valid input is passed to HomePresenter Then displayHomeMetaData should be called",homePresenterOutputSpy.isdisplayHomeMetaDataCalled);
48 | }
49 |
50 | @Test
51 | public void presentHomeMetaData_with_inVaildInput_shouldNotCall_displayHomeMetaData(){
52 | //Given
53 | HomePresenter homePresenter = new HomePresenter();
54 | HomeResponse homeResponse = new HomeResponse();
55 | homeResponse.listOfFlights = null;
56 |
57 | HomePresenterOutputSpy homePresenterOutputSpy = new HomePresenterOutputSpy();
58 | homePresenter.output = new WeakReference(homePresenterOutputSpy);
59 |
60 | //When
61 | homePresenter.presentHomeMetaData(homeResponse);
62 |
63 | //Then
64 | Assert.assertFalse("When the valid input is passed to HomePresenter Then displayHomeMetaData should NOT be called",homePresenterOutputSpy.isdisplayHomeMetaDataCalled);
65 | }
66 |
67 | @Test
68 | public void verify_HomePresenter_getDaysDiff_is_CalcualtedCorrectly_ForFutureTrips(){
69 | //Given
70 | HomePresenter homePresenter = new HomePresenter();
71 | HomeResponse homeResponse = new HomeResponse();
72 |
73 | ArrayList flightsList = new ArrayList<>();
74 |
75 | FlightModel flight1 = new FlightModel();
76 | flight1.flightName = "A5 231";
77 | flight1.startingTime = "2016/10/31";
78 | flight1.numberofSeats = "6";
79 | flight1.gate = "33";
80 | flight1.terminal = "T1";
81 |
82 | flightsList.add(flight1);
83 |
84 |
85 | homeResponse.listOfFlights = flightsList;
86 |
87 | HomePresenterOutputSpy homePresenterOutputSpy = new HomePresenterOutputSpy();
88 | homePresenter.output = new WeakReference(homePresenterOutputSpy);
89 |
90 |
91 | //When
92 | Calendar currentTime = Calendar.getInstance();
93 | currentTime.set(2016,9,12,0,0,0);
94 | homePresenter.setCurrentTime(currentTime);
95 | homePresenter.presentHomeMetaData(homeResponse);
96 |
97 |
98 | //Then
99 | // "It has been " + daysDiff + " days since you flew";
100 | String ExpectedText = "You have " + "19" + " days to fly";
101 | String ActualText = homePresenterOutputSpy.homeViewModelCopy.listOfFlights.get(0).noOfDaysToFly;
102 | Assert.assertEquals("When current date is 2016/10/12 & Flying Date is 2016/10/31 Then no of days should be 19",ExpectedText,ActualText);
103 |
104 | }
105 |
106 | @Test
107 | public void verify_HomePresenter_getDaysDiff_is_CalcualtedCorrectly_ForPastTrips(){
108 | //Given
109 | HomePresenter homePresenter = new HomePresenter();
110 | HomeResponse homeResponse = new HomeResponse();
111 |
112 | ArrayList flightsList = new ArrayList<>();
113 |
114 | FlightModel flight1 = new FlightModel();
115 | flight1.flightName = "A5 231";
116 | flight1.startingTime = "2016/10/01";
117 | flight1.numberofSeats = "6";
118 | flight1.gate = "33";
119 | flight1.terminal = "T1";
120 |
121 | flightsList.add(flight1);
122 |
123 |
124 | homeResponse.listOfFlights = flightsList;
125 |
126 | HomePresenterOutputSpy homePresenterOutputSpy = new HomePresenterOutputSpy();
127 | homePresenter.output = new WeakReference(homePresenterOutputSpy);
128 |
129 |
130 | //When
131 | Calendar currentTime = Calendar.getInstance();
132 | currentTime.set(2016,9,12,0,0,0);
133 | homePresenter.setCurrentTime(currentTime);
134 | homePresenter.presentHomeMetaData(homeResponse);
135 |
136 |
137 | //Then
138 | // "It has been " + daysDiff + " days since you flew";
139 | String ExpectedText = "It has been " + 10 + " days since you flew";
140 | String ActualText = homePresenterOutputSpy.homeViewModelCopy.listOfFlights.get(0).noOfDaysToFly;
141 | Assert.assertEquals("When current date is 2016/10/12 & Flying Date is 2016/10/01 Then no of days should be 12",ExpectedText,ActualText);
142 |
143 | }
144 |
145 | private class HomePresenterOutputSpy implements HomePresenterOutput {
146 | public boolean isdisplayHomeMetaDataCalled = false;
147 | public HomeViewModel homeViewModelCopy;
148 | @Override
149 | public void displayHomeMetaData(HomeViewModel homeViewModel) {
150 | isdisplayHomeMetaDataCalled = true;
151 | homeViewModelCopy = homeViewModel;
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/root/src/app_package/Router.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | import android.content.Intent;
4 | import android.support.annotation.NonNull;
5 | import android.view.View;
6 | import android.widget.AdapterView;
7 |
8 | import java.lang.ref.WeakReference;
9 |
10 |
11 |
12 |
13 |
14 | interface ${classname}RouterInput{
15 | public Intent navigateToSomeWhere(int position);
16 | public void passDataToNextScene(int position, Intent intent);
17 | }
18 |
19 | public class ${classname}Router implements ${classname}RouterInput, AdapterView.OnItemClickListener {
20 |
21 | public static String TAG = ${classname}Router.class.getSimpleName();
22 | public WeakReference<${classname}Activity> activity;
23 |
24 |
25 |
26 |
27 | @NonNull
28 | @Override
29 | public Intent navigateToSomeWhere(int position) {
30 | //Based on the position or someother data decide what is the next scene
31 | //Intent intent = new Intent(activity.get(),NextActivity.class);
32 | //return intent;
33 | return null;
34 | }
35 |
36 | @Override
37 | public void passDataToNextScene(int position, Intent intent) {
38 | //Based on the position or someother data decide the data for the next scene
39 | // ${classname}Model flight = activity.get().listOfSomething.get(position);
40 | // intent.putExtra("flight",flight);
41 | }
42 |
43 | @Override
44 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
45 | // Log.e(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
46 | Intent intent = navigateToSomeWhere(position);
47 | passDataToNextScene(position, intent);
48 | activity.get().startActivity(intent);
49 | }
50 |
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/root/src/app_package/Worker.java.ftl:
--------------------------------------------------------------------------------
1 | package ${packageName};
2 |
3 | interface ${classname}WorkerInput{
4 | //Define needed interfaces
5 | }
6 | public class ${classname}Worker implements ${classname}WorkerInput {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/template.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
21 |
22 |
23 | acc_android_studio.png
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------