--- name: ylgs-android-conventions description: >- Implements and refactors Java code in the Ylgs Android app (ylgs) following project conventions: LatteActivity/ViewBinding, latte-core BaseActivity lifecycle, feature packages (ylpc, tyuan, route, ylgs), Fastjson, GreenDao, tab/fragment patterns, and copy-paste code templates. Use when editing this repository, adding Activities or Fragments, scaffolding new screens, or when the user references 水表普查, ylpc, PcTaskDetailActivity-style code, or 代码模板. --- # Ylgs Android 项目约定 在 `ylgs` 仓库中写或改 Java/Android 代码时,以 `app` 模块里与 **水表普查 / ylpc** 相关的实现为风格基准;**`PcTaskDetailActivity`** 是轻量、典型的 **LatteActivity** 页面示例(无 Presenter、ViewBinding、Tab+Fragment)。 ## 工程与包结构 - **模块**: `:app`(主应用)、`:latte-core`(`LatteActivity`、`BaseActivity`、基础 UI/工具)、`:graffiti` 等。 - **包按业务划分**(同一 app 内勿混用职责): - `com.tofly.ylpc` — 普查等业务 - `com.tofly.tyuan` — 台帐/表务等 - `com.tofly.route` — 路线/任务等 - `com.tofly.ylgs` — 壳、登录、部分通用 Activity - **资源与 ViewBinding**: `R`、`*Binding` 类在 **`com.tofly.ylgs`**(`databinding` 生成于 app 模块),业务类在 `ylpc` 等包中 **import 自 ylgs** 是正常做法。 ## Activity 标准形态(对齐 PcTaskDetailActivity) 1. **基类**: 一般业务页用 **`LatteActivity`**(`com.tofly.latte_core.base`),其继承 `BaseActivity` 并实现 `LatteContract.LatteView`。 2. **布局接入**: 覆写 **`Object setLayout()`**: - ViewBinding:`*Binding.inflate(getLayoutInflater()); return binding.getRoot();` - 或返回 layout 资源 `Integer`(旧式 XML-only)。 3. **Presenter**: 覆写 **`createPresenter()`**。无网络/MVP 需求时 **`return null`**(与 `PcTaskDetailActivity` 一致);非 null 时需在 `onDestroy` 前由基类处理 attach/detach。 4. **初始化**: 把逻辑放在 **`initView()`**(基类在 `onCreate` 中 `setContentView` → ButterKnife → `createPresenter` → **`initView()`** → `initNet()`)。不要绕过该顺序在子类重复 `setContentView`(除非像 `PhotoActivity` 等有特殊 `onCreate` 链的特殊基类)。 5. **类注释**: 文件头使用 Javadoc **`@author`**(项目内常见 `76486` / `ychk`),保持与邻文件一致。 ## Toolbar 与错误提示 - 标题栏:使用基类 **`initBar(Toolbar toolbar, String title)`**;嵌套 toolbar 时常用 **`binding.*Toolbar*.mainToolbar`** 形式(随 layout 命名)。 - 用户可见错误:调用 **`onError(String)`**(`BaseView` → `ToastUtils`),与 `PcTaskDetailActivity` 扫码失败分支一致。 ## Intent / 页面传参 - 简单标量:`getIntent().getLongExtra` / `getIntExtra` / `getBooleanExtra` / `getStringExtra`,key 使用 **字符串字面量**(项目现有风格;新代码若引入常量类需与模块内既有做法一致)。 - **实体跨页**:常用 **`com.alibaba.fastjson.JSON`**: - 传出 / Fragment:`JSON.toJSONString(entity)` - 读入:`JSON.parseObject(json, XxxEntity.class)` - Intent 里可同时带 `pcId` 等,若带 `data` JSON 则可在读库后再覆盖(见 `PcTaskDetailActivity`)。 ## TabLayout + ViewPager + 多 Fragment - 使用 **`ListPageAdapter`**(`FragmentPagerAdapter`):`FragmentManager` + 标题列表 + 可选 **tab 图标 `List`(R.mipmap.*)** + `List`。 - **`setupWithViewPager`** 后,对 tab 设置 **`setCustomView(listPageAdapter.getTabView(i, this))`**。 - **`setOffscreenPageLimit`** 按页数设置,避免相邻页被销毁导致状态丢失(普查示例为 2)。 - 初始页:用业务字段(如 `type == 2`)调用 **`setCurrentItem`**。 ## Fragment 与数据下发 - 新建 Fragment 后将 **`Bundle`** 置于 **`setArguments(bundle)`**,不要在构造里塞大块状态。 - 普查线继承链常见:**`PhotoFragment`**(拍照/地图等)← **`LatteDelegate`**;复杂表单 Fragment 使用对应 **`FragmentXxxBinding`**。 - 从 Activity **转发扫码等结果**到子 Fragment:持有 fragment 引用并调用子类方法(如 `setQc`),与 `PcTaskDetailActivity.onActivityResult` 模式一致。 ## 扫码(ZXing) - `onActivityResult` 中 requestCode **`49374`** 与 **`IntentIntegrator.parseActivityResult`** 配合使用(与 Google ZXing 集成一致);成功则把内容下发到当前业务 Fragment,失败 **`onError("扫码失败")`**。 ## 本地存储(ylpc) - 普查相关实体常用 **`PcDaoUtilsStore.getInstance()`** 等 GreenDao 封装访问(如 `queryById`);路径与实体定义随 **greenDao** 包与 `com.tofly.ylpc.entity` 保持一致。 ## 依赖与 API 习惯(实现时沿用) - **JSON**: Fastjson(`JSON`、`JSONObject`)。 - **异步/生命周期**: `RxAppCompatActivity`(`BaseActivity` 父类);订阅需遵守项目既有 RxLifecycle 用法。 - **图片/相机/地图**: 普查相关多依赖 **`PhotoActivity` / `PhotoFragment`**、`TakePhoto`、高德等——新页面若需同类能力应 **继承对应基类**,而不是在 `LatteActivity` 里堆叠重复逻辑。 ## 代码模板 以下模板为 **`com.tofly.ylpc`(或对应业务包)+ `com.tofly.ylgs.R` / `databinding`** 写法;将占位符替换为实际类名、布局名、实体类与 presenter 实现。布局文件需先在 `app` 工程中创建(如 `activity_xxx.xml`),ViewBinding 类名遵循 Android 命名规则。 ### A. LatteActivity + ViewBinding(无 Presenter,最常用) ```java package com.tofly.ylpc.ui.activity; import com.tofly.latte_core.base.LatteActivity; import com.tofly.latte_core.base.LatteContract; import com.tofly.ylgs.databinding.ActivityXxxPlaceholderBinding; /** * @author 76486 */ public class XxxPlaceholderActivity extends LatteActivity { private ActivityXxxPlaceholderBinding binding; @Override public Object setLayout() { binding = ActivityXxxPlaceholderBinding.inflate(getLayoutInflater()); return binding.getRoot(); } @Override protected LatteContract.LattePresenter createPresenter() { return null; } @Override public void initView() { initBar(binding.llToolbar.mainToolbar, "标题"); // getIntent()、控件事件、RecyclerView 等 } } ``` 说明:`initBar` 第一参随 layout 中 `Toolbar` 实际 id 调整(无嵌套则可能为 `binding.mainToolbar` 等)。 ### B. Activity 跳转并传递实体(Fastjson) ```java Intent intent = new Intent(fromActivity, TargetActivity.class); intent.putExtra("id", entity.getId()); intent.putExtra("data", JSON.toJSONString(entity)); fromActivity.startActivity(intent); ``` 接收端: ```java String data = getIntent().getStringExtra("data"); if (!TextUtils.isEmpty(data)) { WaterMeterEntity entity = JSON.parseObject(data, WaterMeterEntity.class); } ``` ### C. TabLayout + ViewPager + ListPageAdapter(多块 Fragment) ```java import java.util.ArrayList; import java.util.Arrays; import java.util.List; import androidx.fragment.app.Fragment; import com.tofly.ylpc.ui.adapter.ListPageAdapter; // 在 initView() 或独立方法中: List titles = Arrays.asList("标签一", "标签二"); List tabIcons = Arrays.asList(R.mipmap.icon_jc, R.mipmap.icon_wf); // 可改为单页时不传图标逻辑 List fragments = new ArrayList<>(); ExampleFragmentTab0 f0 = new ExampleFragmentTab0(); ExampleFragmentTab1 f1 = new ExampleFragmentTab1(); Bundle bundle = new Bundle(); bundle.putString("data", JSON.toJSONString(modelEntity)); f0.setArguments(bundle); f1.setArguments(bundle); fragments.add(f0); fragments.add(f1); ListPageAdapter adapter = new ListPageAdapter(getSupportFragmentManager(), titles, tabIcons, fragments); binding.viewPager.setAdapter(adapter); binding.viewPager.setOffscreenPageLimit(fragments.size()); binding.tabLayout.setupWithViewPager(binding.viewPager); for (int i = 0; i < binding.tabLayout.getTabCount(); i++) { binding.tabLayout.getTabAt(i).setCustomView(adapter.getTabView(i, this)); } binding.viewPager.setCurrentItem(0); ``` ### D. LatteDelegate / LatteFragment + ViewBinding(含 Presenter) 继承 **`LatteDelegate`**(或项目中的 **`PhotoFragment`** 等你需要的中间基类): ```java package com.tofly.ylpc.ui.fragment; import com.tofly.latte_core.base.LatteContract; import com.tofly.ylgs.databinding.FragmentXxxPlaceholderBinding; /** * @author 76486 */ public class XxxPlaceholderFragment extends LatteDelegate { private FragmentXxxPlaceholderBinding binding; @Override public Object setLayout() { binding = FragmentXxxPlaceholderBinding.inflate(getLayoutInflater()); return binding.getRoot(); } @Override protected LatteContract.LattePresenter createPresenter() { return null; // 若有上传/接口实现则 return new YourImpl(); } @Override public void initView() { Bundle args = getArguments(); if (args != null) { String json = args.getString("data"); // JSON.parseObject(json, ...) } } } ``` 需要拍照/地图等能力时,将 **`extends LatteDelegate`** 改为 **`extends PhotoFragment`**,并补齐父类要求的构造或生命周期(与同目录已有 Fragment 一致)。 ### E. ZXing 扫码回调(Activity 中转 Fragment) ```java import android.content.Intent; import androidx.annotation.Nullable; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; private static final int ZXING_SCAN_REQUEST = 49374; @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == ZXING_SCAN_REQUEST && resultCode == RESULT_OK) { IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); if (result != null && !android.text.TextUtils.isEmpty(result.getContents())) { String text = result.getContents(); if (yourFragment != null) { yourFragment.setQc(text); // 方法名以目标 Fragment 为准,如同模块的 setQc } } else { onError("扫码失败"); } } } ``` 启动扫码:`new IntentIntegrator(this).initiateScan();`(与项目现有写法保持一致)。 ### F. Presenter:仅当页面需要时再实现 若无独立 Presenter,**始终 `createPresenter()` → `null`**。若新建 `LatteContract.LattePresenter` 的实现类(如普查中的 **`UpLoadImpl`**),再在对应 Activity/Delegate 里 `return new XxxImpl()`,并实现契约中的网络方法;不要在模板里虚构方法体,应从同模块 **Impl / Activity** 复制订阅与 `getResult*` 回调模式。 ## 禁止与注意事项 - **不要**在无关文件做“顺手重构”;与原文件作者风格(命名、注释量)对齐。 - **不要**把 skill 里的示例当成唯一 key 名;以**当前调用链与同类页面**为准。 - 新增资源放在 app 模块既有 **drawable/mipmap/layout** 命名习惯下;strings 中文标题与业务用语与普查模块现有一致。 ## 自检清单(新 Activity) - [ ] `setLayout` 返回 Binding root 或 layout id - [ ] `createPresenter` 明确 null 或真实 Presenter - [ ] `initView` 内解析 Intent、初始化 toolbar - [ ] `R` / `Binding` 来自 `com.tofly.ylgs` - [ ] 错误走 `onError`,加载走 `showLoading`/`hideLoading`(若用网络层)