├── .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 | 31 | --------------------------------------------------------------------------------