소스 정보
- 저장소
- alinaqi/maggy
- 최근 소스 활동
- 2026년 4월 7일 05:21
- 감지된 SKILL.md 언어
- 영어
- 스타
- 705
- 포크
- 56
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/alinaqi/maggy --skill android-java명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Universal coding patterns, constraints, TDD workflow, atomic todos
Multi-model validation council — auto-validate plans, architecture changes, and PRs via validate-plan/review before executing
Task-scoped memory lifecycle — typed MnemoGraph prevents lossy context compaction by treating facts/decisions/code-refs/handoffs as distinct node types with per-type eviction policies
SOC 직업 분류 기준
SKILL.md 표시 중
| name | android-java |
| description | Android Java development with MVVM, ViewBinding, and Espresso testing |
| when-to-use | When working on Android Java source files |
| user-invocable | false |
| paths | ["**/*.java","android/**","**/build.gradle"] |
| effort | medium |
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
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'
}
// 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 {
user.setValue(result);
loading.setValue();
}
{
error.setValue(message);
loading.setValue();
}
});
}
}
// 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() != ) {
dao.insert(response.body());
callback.onSuccess(response.body());
} {
callback.onError();
}
}
{
callback.onError(t.getMessage());
}
});
}
}
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;
}
}
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
}
}
@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 {
doAnswer(invocation -> {
Callback<User> callback = invocation.getArgument();
callback.onError();
;
}).(repository).getUser(eq(), any());
viewModel.loadUser();
assertEquals(, viewModel.getError().getValue());
assertFalse(viewModel.isLoading().getValue());
}
}
@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()));
}
}
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:
<?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 -->
android {
lint {
abortOnError true
warningsAsErrors false
checkReleaseBuilds true
xmlReport true
htmlReport true
}
}
// 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());
}
}
// 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
}
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;
}
}