Loading skill
Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Android Java development with MVVM, ViewBinding, and Espresso testing
.claude/skills/alinaqi-android-java/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 189% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 187% | 0% |
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.mdgroovyplugins { 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' }
java// 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); } }); } }
java// 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()); } }); } }
javapublic 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; } }
javapublic 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 } }
java@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()); } }
java@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())); } }
yamlname: 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
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>
groovyandroid { lint { abortOnError true warningsAsErrors false checkReleaseBuilds true xmlReport true htmlReport true } }
java// 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()); } }
java// 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 }
javapublic 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; } }
Other measured skills in the registry, with their headline benchmark lift.