К основному содержанию
правило

android-sdk — правило для Cursor: Android на Java по MVVM

Правило задаёт Cursor стандарты разработки Android на Java: MVVM, Jetpack, Hilt, Room и навигация. Пригодится, когда агент пишет код для Android-проекта.

Установить в Cursor · Вручную: положить файлы Все способы и платформы ↓
.cursor/rules/android-sdk.mdc
Открыть на GitHub

Что делает android-sdk

Это .mdc-правило с глобами **/*.java, поэтому Cursor подхватывает его для Java-файлов Android-проекта. Внутри — набор практик: модульная архитектура на MVVM, разбивка пакетов по фичам, а не по типам компонентов. Для каждой темы показаны плохой и хороший пример кода.

Из Jetpack правило требует Room для локальной базы, View Binding вместо findViewById и Navigation Component вместо ручных транзакций фрагментов. Внедрение зависимостей — через Hilt поверх Dagger. Примеры в тексте обрываются на модуле AppModule, так что по DI и тестированию опирайтесь на собственные знания.

Правило рассчитано на Java. Если проект на Kotlin, эти примеры и советы по AndroidViewModel вам не подойдут — ищите Kotlin-версию.

Какие задачи решает

  • увести агента от прямых сетевых вызовов в Activity
  • разложить пакеты по фичам, а не по activities и fragments
  • заменить сырой SQLiteOpenHelper на Room
  • убрать findViewById через View Binding
  • перевести навигацию на Navigation Component

Примеры запросов агенту

  • Отрефактори UserProfileActivity на MVVM с ViewModel и репозиторием
  • Перепиши этот экран на View Binding вместо findViewById
  • Замени ручные транзакции фрагментов на Navigation Component

Как установить android-sdk

Вручную: положить файлы

.cursor/rules/android-sdk.mdc

Во frontmatter: description, globs, alwaysApply

По шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules

Вопросы о android-sdk

Как установить android-sdk в Cursor?

Создайте файл .cursor/rules/android-sdk.mdc в корне проекта и вставьте в него содержимое правила. Cursor подхватит его автоматически.

Что делает android-sdk?

Задаёт Cursor правила написания Android-кода на Java: MVVM, Jetpack-библиотеки, Hilt, Room, View Binding и Navigation Component. Агент опирается на них, когда правит Java-файлы.

Подойдёт ли правило для проекта на Kotlin?

Нет. Примеры и рекомендации написаны под Java, включая AndroidViewModel и работу с потоками. Для Kotlin нужен отдельный набор правил.

Исходный текст

android-sdk.mdc · CC0-1.0
---
description: This guide provides definitive best practices for Android SDK development in Java, focusing on modern architecture, Jetpack libraries, performance, security, and testing.
globs: **/*.java
---
# android-sdk Best Practices (Java)

This guide outlines the essential practices for building robust, performant, and maintainable Android applications using Java, adhering to modern standards and leveraging the AndroidX ecosystem.

## 1. Code Organization and Structure

**1.1 Modular Architecture (MVVM)**
Adopt a clean, modular architecture, with MVVM (Model-View-ViewModel) as the standard. This promotes separation of concerns, testability, and scalability.

❌ **BAD: Tight Coupling**
```java
// Activity directly fetching data and updating UI
public class UserProfileActivity extends AppCompatActivity {
    private TextView userName;
    // ... other UI elements

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_profile);
        userName = findViewById(R.id.user_name);
        // Direct network call on UI thread (or poorly managed background thread)
        new Thread(() -> {
            String name = fetchUserNameFromNetwork(); // Blocking call
            runOnUiThread(() -> userName.setText(name));
        }).start();
    }
    // ...
}
```

✅ **GOOD: MVVM with Repository Pattern**
```java
// ui/UserProfileActivity.java
public class UserProfileActivity extends AppCompatActivity {
    private UserProfileViewModel viewModel;
    private TextView userName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_profile);
        userName = findViewById(R.id.user_name);

        viewModel = new ViewModelProvider(this).get(UserProfileViewModel.class);
        viewModel.getUserName().observe(this, name -> {
            userName.setText(name);
        });
    }
}

// ui/UserProfileViewModel.java
public class UserProfileViewModel extends AndroidViewModel {
    private UserRepository userRepository;
    private MutableLiveData<String> userName = new MutableLiveData<>();

    public UserProfileViewModel(@NonNull Application application) {
        super(application);
        this.userRepository = new UserRepository(application); // Injected via Hilt in real app
        loadUserName();
    }

    public LiveData<String> getUserName() {
        return userName;
    }

    private void loadUserName() {
        // Use ExecutorService or RxJava for background work
        Executors.newSingleThreadExecutor().execute(() -> {
            String name = userRepository.fetchUserName();
            userName.postValue(name); // Post value to main thread
        });
    }
}

// data/UserRepository.java
public class UserRepository {
    // In a real app, this would interact with Room, Retrofit, etc.
    public String fetchUserName() {
        // Simulate network call
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        return "John Doe";
    }
}
```

**1.2 Package Structure**
Organize packages by feature or layer, not by Android component type.

❌ **BAD: Component-based**
```
com.example.app
├── activities
│   └── UserProfileActivity.java
├── fragments
│   └── SettingsFragment.java
├── adapters
│   └── UserListAdapter.java
└── utils
    └── NetworkUtils.java
```

✅ **GOOD: Feature-based (or Layer-based for larger apps)**
```
com.example.app
├── userprofile
│   ├── UserProfileActivity.java
│   ├── UserProfileViewModel.java
│   └── UserProfileFragment.java
├── settings
│   ├── SettingsActivity.java
│   └── SettingsViewModel.java
├── data
│   ├── UserRepository.java
│   ├── UserApiService.java
│   └── UserDatabase.java
└── common
    ├── adapters
    │   └── UserListAdapter.java
    └── utils
        └── NetworkUtils.java
```

## 2. Common Patterns and Anti-patterns

**2.1 Jetpack Libraries**
Always prefer AndroidX Jetpack libraries for modern, backward-compatible, and lifecycle-aware components.

*   **Room for Persistence:** Use Room for all local database storage.
    ❌ **BAD: Raw SQLiteOpenHelper**
    ```java
    // Manual SQLite table creation and query management
    public class MyDbHelper extends SQLiteOpenHelper { /* ... */ }
    ```
    ✅ **GOOD: Room Database**
    ```java
    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }

    @Entity
    public class User {
        @PrimaryKey public int id;
        public String name;
    }

    @Dao
    public interface UserDao {
        @Query("SELECT * FROM user WHERE id = :userId")
        LiveData<User> getUserById(int userId);

        @Insert
        void insert(User user);
    }
    ```

*   **View Binding (or Data Binding):** Eliminate `findViewById` boilerplate.
    ❌ **BAD: Manual View Lookup**
    ```java
    public class MyActivity extends AppCompatActivity {
        private Button myButton;
        @Override protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my);
            myButton = findViewById(R.id.my_button);
            myButton.setOnClickListener(v -> /* ... */);
        }
    }
    ```
    ✅ **GOOD: View Binding**
    ```java
    public class MyActivity extends AppCompatActivity {
        private ActivityMyBinding binding; // Generated binding class
        @Override protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            binding = ActivityMyBinding.inflate(getLayoutInflater());
            setContentView(binding.getRoot());
            binding.myButton.setOnClickListener(v -> /* ... */);
        }
    }
    ```

*   **Navigation Component:** Manage in-app navigation consistently.
    ❌ **BAD: Manual Fragment Transactions**
    ```java
    getSupportFragmentManager().beginTransaction()
        .replace(R.id.fragment_container, new DetailFragment())
        .addToBackStack(null)
        .commit();
    ```
    ✅ **GOOD: Navigation Graph**
    ```java
    // In Activity/Fragment
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    navController.navigate(R.id.action_homeFragment_to_detailFragment);
    // Define navigation in res/navigation/nav_graph.xml
    ```

**2.2 Dependency Injection (Hilt)**
Use Hilt (built on Dagger) for robust dependency injection, simplifying object creation and improving testability.

❌ **BAD: Manual Dependency Management**
```java
public class MyRepository {
    private MyApiService apiService = new MyApiService(); // Hardcoded dependency
    // ...
}
```
✅ **GOOD: Hilt**
```java
@Module
@InstallIn(SingletonComponent.class)
public abstract class AppModule {
    @Provides
    @Singleton
    public static MyApiService provideMyApiService() {
        return new MyApiService(); // Or provide Retrofit instance
    }
}

@Singleton
public class MyRepository {
    private final MyApiService apiService;

    @Inject // Hilt injects MyApiService
    public MyRepository(MyApiService apiService) {
        this.apiService = apiService;
    }
    // ...
}

@HiltAndroidApp
public class MyApplication extends Application {}
```

## 3. Performance Considerations

**3.1 Layout Optimization**
Flatten view hierarchies using `ConstraintLayout` and `ViewStub` to reduce layout inflation time and memory usage.

❌ **BAD: Deeply Nested Layouts**
```xml
<LinearLayout orientation="vertical">
    <LinearLayout orientation="horizontal">
        <TextView />
        <ImageView />
    </LinearLayout>
    <LinearLayout orientation="vertical">
        <TextView />
        <Button />
    </LinearLayout>
</LinearLayout>
```
✅ **GOOD: ConstraintLayout**
```xml
<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView app:layout_constraintTop_toTopOf="parent" />
    <ImageView app:layout_constraintBaseline_toBaselineOf="@id/textView" />
    <TextView app:layout_constraintTop_toBottomOf="@id/imageView" />
    <Button app:layout_constraintTop_toBottomOf="@id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
```

**3.2 Image Loading**
Always use a dedicated image loading library like Glide for efficient image fetching, caching, and display.

❌ **BAD: Manual Bitmap Loading**
```java
// Prone to OOM errors, poor caching, complex lifecycle management
Bitmap bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
imageView.setImageBitmap(bitmap);
```
✅ **GOOD: Glide**
```java
Glide.with(context)
     .load("https://example.com/image.jpg")
     .placeholder(R.drawable.placeholder)
     .error(R.drawable.error)
     .into(imageView);
```

**3.3 Background Processing**
Use WorkManager for deferrable, guaranteed background tasks. For immediate, short-lived tasks, use `ExecutorService`.

❌ **BAD: Inconsistent Background Tasks**
```java
// Using AlarmManager for non-exact tasks, or custom Services for simple work
new AsyncTask<Void, Void, Void>() { /* ... */ }.execute(); // Deprecated
```
✅ **GOOD: WorkManager**
```java
// Define a WorkRequest
OneTimeWorkRequest uploadWorkRequest =
    new OneTimeWorkRequest.Builder(UploadWorker.class)
        .setConstraints(new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build())
        .build();

// Enqueue the work
WorkManager.getInstance(context).enqueue(uploadWorkRequest);

// Your Worker class
public class UploadWorker extends Worker {
    public UploadWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        // Perform long-running task
        return Result.success();
    }
}
```

## 4. Common Pitfalls and Gotchas

**4.1 Context Leaks**
Avoid holding strong references to `Activity` or `Fragment` contexts in long-lived objects. Use `ApplicationContext` when a long-lived context is truly needed, or weak references.

❌ **BAD: Static Context Reference**
```java
public class BadManager {
    private static Context context; // LEAK!
    public static void init(Context ctx) { context = ctx; }
}
```
✅ **GOOD: Application Context or No Context**
```java
public class GoodManager {
    private Context appContext; // Use application context for long-lived needs
    public GoodManager(Context context) {
        this.appContext = context.getApplicationContext();
    }
    // Or, better yet, pass only what's needed, not the whole context
    public void doSomething(String data) { /* ... */ }
}
```

**4.2 Security Best Practices**
Implement Android's security guidelines for inter-app communication and network security.

*   **Explicit Intents with Choosers:** For sensitive implicit intents.
    ❌ **BAD: Implicit Intent for Sensitive Data**
    ```java
    Intent sendIntent = new Intent(Intent.ACTION_SEND);
    sendIntent.putExtra(Intent.EXTRA_TEXT, "Sensitive data");
    sendIntent.setType("text/plain");
    startActivity(sendIntent); // Any app can intercept
    ```
    ✅ **GOOD: App Chooser**
    ```java
    Intent sendIntent = new Intent(Intent.ACTION_SEND);
    sendIntent.putExtra(Intent.EXTRA_TEXT, "Sensitive data");
    sendIntent.setType("text/plain");
    Intent chooser = Intent.createChooser(sendIntent, getResources().getString(R.string.chooser_title));
    startActivity(chooser);
    ```

*   **Non-Exported Content Providers:** Prevent unauthorized access.
    ❌ **BAD: Exported Content Provider (default on older Android)**
    ```xml
    <provider android:name=".MyContentProvider" android:authorities="com.example.app.provider" />
    ```
    ✅ **GOOD: Non-Exported Content Provider**
    ```xml
    <provider android:name=".MyContentProvider" android:authorities="com.example.app.provider" android:exported="false" />
    ```

*   **Network Security Configuration:** Enforce HTTPS and manage trust anchors.
    ❌ **BAD: Allowing Cleartext Traffic**
    ```xml
    <network-security-config>
        <base-config cleartextTrafficPermitted="true">
            <trust-anchors>
                <certificates src="system" />
            </trust-anchors>
        </base-config>
    </network-security-config>
    ```
    ✅ **GOOD: Disabling Cleartext Traffic**
    ```xml
    <!-- res/xml/network_security_config.xml -->
    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">api.example.com</domain>
            <domain includeSubdomains="true">secure.example.com</domain>
        </domain-config>
        <!-- Use debug-overrides for development only -->
        <debug-overrides>
            <trust-anchors>
                <certificates src="user"/>
            </trust-anchors>
        </debug-overrides>
    </network-security-config>
    ```
    And in `AndroidManifest.xml`:
    ```xml
    <application android:networkSecurityConfig="@xml/network_security_config" ...>
    ```

**4.3 Ignoring Lint Warnings**
Treat all lint warnings as errors. Android Studio's lint tool catches critical issues from performance to security.

❌ **BAD: Shipping with Lint Warnings**
```
> Task :app:lintDebug
...
W: /path/to/project/app/src/main/java/com/example/app/MainActivity.java:123: Warning: Hardcoded string "Hello" should use @string resource [HardcodedText]
```
✅ **GOOD: Resolve All Lint Issues**
Configure Gradle to fail builds on lint errors:
```gradle
android {
    lintOptions {
        abortOnError true // Fail the build if lint finds errors
        warningsAsErrors true // Treat warnings as errors
    }
}
```

## 5. Testing Approaches

**5.1 Unit Tests**
Focus on testing business logic, ViewModels, and Repositories in isolation on the JVM. Use JUnit and Mockito.

```java
// test/com/example/app/userprofile/UserProfileViewModelTest.java
public class UserProfileViewModelTest {
    @Rule
    public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); // For LiveData

    @Mock
    private UserRepository userRepository;
    @Mock
    private Application application; // Mock application context

    private UserProfileViewModel viewModel;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        viewModel = new UserProfileViewModel(application);
        // Inject mock repository (in real app, Hilt would handle this)
        ReflectionHelpers.setField(viewModel, "userRepository", userRepository);
    }

    @Test
    public void getUserName_returnsCorrectName() {
        // Given
        when(userRepository.fetchUserName()).thenReturn("Test User");

        // When
        viewModel.loadUserName(); // Trigger data load

        // Then
        assertEquals("Test User", viewModel.getUserName().getValue());
    }
}
```

**5.2 Integration Tests**
Test interactions between components (e.g., Repository with Room database, or API service with Retrofit). Run on an Android emulator or device.

```java
// androidTest/com/example/app/data/UserDaoTest.java
@RunWith(AndroidJUnit4.class)
public class UserDaoTest {
    @Rule
    public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); // For LiveData

    private AppDatabase database;
    private UserDao userDao;

    @Before
    public void createDb() {
        Context context = ApplicationProvider.getApplicationContext();
        database = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
        userDao = database.userDao();
    }

    @After
    public void closeDb() {
        database.close();
    }

    @Test
    public void writeUserAndReadInList() {
        User user = new User();
        user.id = 1;
        user.name = "Test User";
        userDao.insert(user);

        User byId = LiveDataTestUtil.getValue(userDao.getUserById(1));
        assertEquals(user.name, byId.name);
    }
}
```

**5.3 UI Tests (Espresso)**
Verify user flows and UI correctness. Run on an Android emulator or device.

```java
// androidTest/com/example/app/UserProfileActivityTest.java
@RunWith(AndroidJUnit4.class)
@LargeTest
public class UserProfileActivityTest {
    @Rule
    public ActivityScenarioRule<UserProfileActivity> activityRule =
            new ActivityScenarioRule<>(UserProfileActivity.class);

    @Test
    public void userNameIsDisplayed() {
        // Assuming the ViewModel loads "John Doe"
        onView(withId(R.id.user_name))
            .check(matches(withText("John Doe")));
    }
}
```

**5.4 Automated Testing**
Integrate all tests (unit, integration, UI, lint) into your CI/CD pipeline.

```bash
# Run lint checks
./gradlew lintDebug

# Run unit tests
./gradlew testDebugUnitTest

# Run connected (integration/UI) tests on connected device/emulator
./gradlew connectedDebugAndroidTest
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Похожие правила IDE

Всё по теме «Мобильная разработка» →
android-kotlin-development aj-geddes Скилл для ИИ-агента, который ведёт разработку нативного Android-приложения на Kotlin: MVVM, Compose, Retrofit, Room. CX Codex +1 1 500установок android-java alinaqi Скилл для работы с Android-проектами на Java: подсказывает структуру MVVM, ViewBinding и тесты на Espresso. CX Codex +1 667установок android-clean-architecture affaan-m Подсказывает агенту, как раскладывать Android- и KMP-проект по слоям: модули, зависимости, UseCase, репозитории, работа с Room, SQLDelight и Ktor. CX Codex +1 5 200установок android-development dpconde Скилл ведёт агента по официальной архитектуре Android: модули, Jetpack Compose, Hilt, Room. Пригодится, когда проект на Kotlin растёт и пора навести порядок в слоях. CX Codex +1 718установок sentry-android-sdk getsentry Скилл сканирует проект на Android и пошагово настраивает отслеживание ошибок, трейсинг и сессии в Sentry. Пригодится при подключении мониторинга в кодовую базу на Kotlin или Java. CX Codex +1 2 300установок claude-android-ninja DrJacky Помогает агенту собирать и переносить Android-приложения на Kotlin, Compose, Hilt и Room 3 без выдуманных API. CX Codex +1 604установок

Из того же набора69

azure Правило для Cursor с набором требований к коду и инфраструктуре в Azure: IaC, безопасность, экономия ресурсов. Пригодится, если проект живёт в Azure. ★ 3 579звёзд репо aws Задаёт агенту стандарты разработки на AWS: только CDK v2, переиспользование окружения Lambda, идемпотентность и минимальные права IAM. ★ 3 579звёзд репо aws-ecs Правило для Cursor с набором требований к контейнерам на AWS ECS: неизменяемые образы, секреты из Secrets Manager, логи в stdout. Пригодится, когда агент пишет Dockerfile и task definition. ★ 3 579звёзд репо aws-amplify Правило задаёт Cursor стиль работы с AWS Amplify Gen 2: отдельные файлы ресурсов, модульные функции, типизированные запросы к данным. Пригодится, если бэкенд на Amplify растёт и хочется держать его в порядке. ★ 3 579звёзд репо asyncio Правило для Cursor задаёт, как писать асинхронный код на Python: единая точка входа, задачи без утечек, никаких блокировок цикла событий. ★ 3 579звёзд репо apollo-graphql Правило для Cursor задаёт порядок работы с Apollo GraphQL: сначала схема на SDL, потом резолверы. Пригодится, если в проекте JS или JSX. ★ 3 579звёзд репо ant-design Правило задаёт агенту стандарты работы с Ant Design v6.x: тему через токены, сетку, доступность компонентов. Пригодится, если проект на React и Ant Design. ★ 3 579звёзд репо aiohttp Правило для Cursor задаёт, как писать асинхронный HTTP-клиент на aiohttp: одна сессия на приложение, таймауты, обработка ошибок. ★ 3 579звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой