Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    alinaqi

    android-java

    alinaqi/android-java
    Coding
    467

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Android Java development with MVVM, ViewBinding, and Espresso testing

    SKILL.md

    Android Java Skill


    Project Structure

    project/
    ├── app/
    │   ├── src/
    │   │   ├── main/
    │   │   │   ├── java/com/example/app/
    │   │   │   │   ├── data/           # Data layer
    │   │   │   │   │   ├── local/      # Room database, SharedPreferences
    │   │   │   │   │   ├── remote/     # Retrofit services, API clients
    │   │   │   │   │   └── repository/ # Repository implementations
    │   │   │   │   ├── di/             # Dependency injection (Hilt/Dagger)
    │   │   │   │   ├── domain/         # Business logic
    │   │   │   │   │   ├── model/      # Domain models
    │   │   │   │   │   ├── repository/ # Repository interfaces
    │   │   │   │   │   └── usecase/    # Use cases
    │   │   │   │   ├── ui/             # Presentation layer
    │   │   │   │   │   ├── feature/    # Feature screens
    │   │   │   │   │   │   ├── FeatureActivity.java
    │   │   │   │   │   │   ├── FeatureFragment.java
    │   │   │   │   │   │   └── FeatureViewModel.java
    │   │   │   │   │   └── common/     # Shared UI components
    │   │   │   │   └── App.java        # Application class
    │   │   │   ├── res/
    │   │   │   │   ├── layout/
    │   │   │   │   ├── values/
    │   │   │   │   └── drawable/
    │   │   │   └── AndroidManifest.xml
    │   │   ├── test/                   # Unit tests
    │   │   └── androidTest/            # Instrumentation tests
    │   └── build.gradle
    ├── build.gradle                    # Project-level build file
    ├── gradle.properties
    ├── settings.gradle
    └── CLAUDE.md
    

    Gradle Configuration

    App-level build.gradle

    plugins {
        id 'com.android.application'
    }
    
    android {
        namespace 'com.example.app'
        compileSdk 34
    
        defaultConfig {
            applicationId "com.example.app"
            minSdk 24
            targetSdk 34
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_17
            targetCompatibility JavaVersion.VERSION_17
        }
    
        buildFeatures {
            viewBinding true
        }
    }
    
    dependencies {
        // AndroidX
        implementation 'androidx.core:core:1.12.0'
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.11.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    
        // Lifecycle
        implementation 'androidx.lifecycle:lifecycle-viewmodel:2.7.0'
        implementation 'androidx.lifecycle:lifecycle-livedata:2.7.0'
    
        // Testing
        testImplementation 'junit:junit:4.13.2'
        testImplementation 'org.mockito:mockito-core:5.8.0'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    }
    

    Architecture Patterns

    MVVM with ViewModel

    // ViewModel - holds UI state, survives configuration changes
    public class UserViewModel extends ViewModel {
        private final UserRepository repository;
        private final MutableLiveData<User> user = new MutableLiveData<>();
        private final MutableLiveData<Boolean> loading = new MutableLiveData<>(false);
        private final MutableLiveData<String> error = new MutableLiveData<>();
    
        public UserViewModel(UserRepository repository) {
            this.repository = repository;
        }
    
        public LiveData<User> getUser() {
            return user;
        }
    
        public LiveData<Boolean> isLoading() {
            return loading;
        }
    
        public LiveData<String> getError() {
            return error;
        }
    
        public void loadUser(String userId) {
            loading.setValue(true);
            repository.getUser(userId, new Callback<User>() {
                @Override
                public void onSuccess(User result) {
                    user.setValue(result);
                    loading.setValue(false);
                }
    
                @Override
                public void onError(String message) {
                    error.setValue(message);
                    loading.setValue(false);
                }
            });
        }
    }
    

    Repository Pattern

    // Repository interface (domain layer)
    public interface UserRepository {
        void getUser(String userId, Callback<User> callback);
        void saveUser(User user, Callback<Void> callback);
    }
    
    // Repository implementation (data layer)
    public class UserRepositoryImpl implements UserRepository {
        private final UserApi api;
        private final UserDao dao;
    
        public UserRepositoryImpl(UserApi api, UserDao dao) {
            this.api = api;
            this.dao = dao;
        }
    
        @Override
        public void getUser(String userId, Callback<User> callback) {
            // Try cache first, then network
            User cached = dao.getUserById(userId);
            if (cached != null) {
                callback.onSuccess(cached);
                return;
            }
            api.getUser(userId).enqueue(new retrofit2.Callback<User>() {
                @Override
                public void onResponse(Call<User> call, Response<User> response) {
                    if (response.isSuccessful() && response.body() != null) {
                        dao.insert(response.body());
                        callback.onSuccess(response.body());
                    } else {
                        callback.onError("Failed to load user");
                    }
                }
    
                @Override
                public void onFailure(Call<User> call, Throwable t) {
                    callback.onError(t.getMessage());
                }
            });
        }
    }
    

    Activity & Fragment Patterns

    Activity with ViewBinding

    public class MainActivity extends AppCompatActivity {
        private ActivityMainBinding binding;
        private MainViewModel viewModel;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = ActivityMainBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
    
            viewModel = new ViewModelProvider(this).get(MainViewModel.class);
            setupObservers();
            setupListeners();
        }
    
        private void setupObservers() {
            viewModel.getUser().observe(this, user -> {
                binding.userName.setText(user.getName());
            });
    
            viewModel.isLoading().observe(this, isLoading -> {
                binding.progressBar.setVisibility(isLoading ? View.VISIBLE : View.GONE);
            });
        }
    
        private void setupListeners() {
            binding.refreshButton.setOnClickListener(v -> {
                viewModel.loadUser(getCurrentUserId());
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            binding = null;
        }
    }
    

    Fragment with ViewBinding

    public class UserFragment extends Fragment {
        private FragmentUserBinding binding;
        private UserViewModel viewModel;
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            binding = FragmentUserBinding.inflate(inflater, container, false);
            return binding.getRoot();
        }
    
        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            viewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);
            setupObservers();
        }
    
        private void setupObservers() {
            viewModel.getUser().observe(getViewLifecycleOwner(), user -> {
                binding.userName.setText(user.getName());
            });
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            binding = null;  // Prevent memory leaks
        }
    }
    

    Testing

    Unit Tests with JUnit & Mockito

    @RunWith(MockitoJUnitRunner.class)
    public class UserViewModelTest {
        @Mock
        private UserRepository repository;
    
        @Rule
        public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
    
        private UserViewModel viewModel;
    
        @Before
        public void setup() {
            viewModel = new UserViewModel(repository);
        }
    
        @Test
        public void loadUser_success_updatesUserLiveData() {
            // Arrange
            User expectedUser = new User("1", "John Doe");
            doAnswer(invocation -> {
                Callback<User> callback = invocation.getArgument(1);
                callback.onSuccess(expectedUser);
                return null;
            }).when(repository).getUser(eq("1"), any());
    
            // Act
            viewModel.loadUser("1");
    
            // Assert
            assertEquals(expectedUser, viewModel.getUser().getValue());
            assertFalse(viewModel.isLoading().getValue());
        }
    
        @Test
        public void loadUser_error_updatesErrorLiveData() {
            // Arrange
            doAnswer(invocation -> {
                Callback<User> callback = invocation.getArgument(1);
                callback.onError("Network error");
                return null;
            }).when(repository).getUser(eq("1"), any());
    
            // Act
            viewModel.loadUser("1");
    
            // Assert
            assertEquals("Network error", viewModel.getError().getValue());
            assertFalse(viewModel.isLoading().getValue());
        }
    }
    

    Instrumentation Tests with Espresso

    @RunWith(AndroidJUnit4.class)
    public class MainActivityTest {
        @Rule
        public ActivityScenarioRule<MainActivity> activityRule =
                new ActivityScenarioRule<>(MainActivity.class);
    
        @Test
        public void userName_isDisplayed() {
            onView(withId(R.id.userName))
                    .check(matches(isDisplayed()));
        }
    
        @Test
        public void refreshButton_click_triggersRefresh() {
            onView(withId(R.id.refreshButton))
                    .perform(click());
    
            onView(withId(R.id.progressBar))
                    .check(matches(isDisplayed()));
        }
    
        @Test
        public void userList_scrollToItem_displaysCorrectly() {
            onView(withId(R.id.userList))
                    .perform(RecyclerViewActions.scrollToPosition(10));
    
            onView(withText("User 10"))
                    .check(matches(isDisplayed()));
        }
    }
    

    GitHub Actions

    name: Android CI
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up JDK 17
            uses: actions/setup-java@v4
            with:
              java-version: '17'
              distribution: 'temurin'
    
          - name: Setup Gradle
            uses: gradle/actions/setup-gradle@v3
    
          - name: Grant execute permission for gradlew
            run: chmod +x gradlew
    
          - name: Run Lint
            run: ./gradlew lint
    
          - name: Run Unit Tests
            run: ./gradlew testDebugUnitTest
    
          - name: Build Debug APK
            run: ./gradlew assembleDebug
    
          - name: Upload APK
            uses: actions/upload-artifact@v4
            with:
              name: debug-apk
              path: app/build/outputs/apk/debug/app-debug.apk
    
      instrumentation-tests:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Set up JDK 17
            uses: actions/setup-java@v4
            with:
              java-version: '17'
              distribution: 'temurin'
    
          - name: Enable KVM
            run: |
              echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
              sudo udevadm control --reload-rules
              sudo udevadm trigger --name-match=kvm
    
          - name: Run Instrumentation Tests
            uses: reactivecircus/android-emulator-runner@v2
            with:
              api-level: 29
              script: ./gradlew connectedDebugAndroidTest
    

    Lint Configuration

    lint.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <lint>
        <!-- Treat these as errors -->
        <issue id="HardcodedText" severity="error" />
        <issue id="MissingTranslation" severity="error" />
        <issue id="UnusedResources" severity="warning" />
    
        <!-- Memory leak detection -->
        <issue id="StaticFieldLeak" severity="error" />
    
        <!-- Security -->
        <issue id="HardcodedDebugMode" severity="error" />
        <issue id="AllowBackup" severity="warning" />
    
        <!-- Performance -->
        <issue id="ViewHolder" severity="error" />
        <issue id="Overdraw" severity="warning" />
    
        <!-- Ignore for tests -->
        <issue id="InvalidPackage">
            <ignore path="**/test/**" />
            <ignore path="**/androidTest/**" />
        </issue>
    </lint>
    

    build.gradle lint options

    android {
        lint {
            abortOnError true
            warningsAsErrors false
            checkReleaseBuilds true
            xmlReport true
            htmlReport true
        }
    }
    

    Common Patterns

    Null-Safe Callbacks

    // Define callback interface
    public interface Callback<T> {
        void onSuccess(T result);
        void onError(String message);
    }
    
    // Use with null checks
    public void fetchData(Callback<Data> callback) {
        if (callback == null) return;
    
        try {
            Data result = performFetch();
            callback.onSuccess(result);
        } catch (Exception e) {
            callback.onError(e.getMessage());
        }
    }
    

    Safe Context Usage

    // Use application context for long-lived objects
    public class DataManager {
        private final Context appContext;
    
        public DataManager(Context context) {
            // Always use application context to prevent Activity leaks
            this.appContext = context.getApplicationContext();
        }
    }
    
    // Check for null context in callbacks
    private void updateUI() {
        Context context = getContext();
        if (context == null || !isAdded()) return;
        // Safe to use context
    }
    

    Thread-Safe Singleton

    public class ApiClient {
        private static volatile ApiClient instance;
        private final Retrofit retrofit;
    
        private ApiClient() {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
    
        public static ApiClient getInstance() {
            if (instance == null) {
                synchronized (ApiClient.class) {
                    if (instance == null) {
                        instance = new ApiClient();
                    }
                }
            }
            return instance;
        }
    }
    

    Android Anti-Patterns

    • ❌ Context leaks - Never hold Activity/Fragment references in static fields or singletons
    • ❌ Memory leaks in callbacks - Always use WeakReference or clear callbacks in onDestroy
    • ❌ UI updates on background thread - Always post to main thread for UI changes
    • ❌ Hardcoded strings - Use string resources for all user-visible text
    • ❌ God Activities - Keep Activities under 200 lines, extract logic to ViewModels
    • ❌ NetworkOnMainThreadException - Never perform network calls on main thread
    • ❌ Ignoring lifecycle - Always respect Activity/Fragment lifecycle states
    • ❌ Blocking the main thread - Keep main thread operations under 16ms
    • ❌ Not handling configuration changes - Use ViewModel to survive rotation
    • ❌ Hardcoded dimensions - Use dp/sp units and dimension resources
    • ❌ Deep view hierarchies - Keep layout depth under 10 levels, use ConstraintLayout
    • ❌ Not closing resources - Always close Cursor, InputStream, database connections
    Recommended Servers
    Postman
    Postman
    Svelte
    Svelte
    Vercel Grep
    Vercel Grep
    Repository
    alinaqi/claude-bootstrap
    Files